@ai-stack/payloadcms 3.68.0-beta.1 → 3.68.0-beta.3

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 (134) 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 +0 -3
  6. package/dist/ai/core/streamObject.js.map +1 -1
  7. package/dist/ai/providers/blocks/anthropic.js +2 -1
  8. package/dist/ai/providers/blocks/anthropic.js.map +1 -1
  9. package/dist/ai/providers/blocks/elevenlabs.js +3 -2
  10. package/dist/ai/providers/blocks/elevenlabs.js.map +1 -1
  11. package/dist/ai/providers/blocks/fal.js +2 -1
  12. package/dist/ai/providers/blocks/fal.js.map +1 -1
  13. package/dist/ai/providers/blocks/google.js +11 -6
  14. package/dist/ai/providers/blocks/google.js.map +1 -1
  15. package/dist/ai/providers/blocks/openai-compatible.js +2 -1
  16. package/dist/ai/providers/blocks/openai-compatible.js.map +1 -1
  17. package/dist/ai/providers/blocks/openai.js +3 -2
  18. package/dist/ai/providers/blocks/openai.js.map +1 -1
  19. package/dist/ai/providers/blocks/xai.js +2 -1
  20. package/dist/ai/providers/blocks/xai.js.map +1 -1
  21. package/dist/ai/providers/icons.d.ts +7 -0
  22. package/dist/ai/providers/icons.js +9 -0
  23. package/dist/ai/providers/icons.js.map +1 -0
  24. package/dist/ai/providers/registry.js +34 -23
  25. package/dist/ai/providers/registry.js.map +1 -1
  26. package/dist/collections/AISettings.js +44 -17
  27. package/dist/collections/AISettings.js.map +1 -1
  28. package/dist/collections/Instructions.js +37 -0
  29. package/dist/collections/Instructions.js.map +1 -1
  30. package/dist/defaults.d.ts +1 -0
  31. package/dist/defaults.js +8 -0
  32. package/dist/defaults.js.map +1 -1
  33. package/dist/endpoints/chat.d.ts +4 -0
  34. package/dist/endpoints/fetchFields.js +10 -0
  35. package/dist/endpoints/fetchFields.js.map +1 -1
  36. package/dist/endpoints/index.js +86 -10
  37. package/dist/endpoints/index.js.map +1 -1
  38. package/dist/exports/fields.d.ts +1 -0
  39. package/dist/exports/fields.js +1 -0
  40. package/dist/exports/fields.js.map +1 -1
  41. package/dist/fields/ArrayComposeField/ArrayComposeField.d.ts +15 -0
  42. package/dist/fields/ArrayComposeField/ArrayComposeField.js +87 -0
  43. package/dist/fields/ArrayComposeField/ArrayComposeField.js.map +1 -0
  44. package/dist/fields/ArrayComposeField/ArrayComposeField.jsx +73 -0
  45. package/dist/fields/PromptEditorField/PromptEditorField.js +7 -2
  46. package/dist/fields/PromptEditorField/PromptEditorField.js.map +1 -1
  47. package/dist/fields/PromptEditorField/PromptEditorField.jsx +5 -2
  48. package/dist/index.d.ts +2 -0
  49. package/dist/index.js +1 -0
  50. package/dist/index.js.map +1 -1
  51. package/dist/payload-ai.d.ts +149 -0
  52. package/dist/plugin.js +16 -32
  53. package/dist/plugin.js.map +1 -1
  54. package/dist/providers/InstructionsProvider/InstructionsProvider.js +47 -15
  55. package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
  56. package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +39 -16
  57. package/dist/providers/InstructionsProvider/context.d.ts +3 -0
  58. package/dist/providers/InstructionsProvider/context.js +2 -0
  59. package/dist/providers/InstructionsProvider/context.js.map +1 -1
  60. package/dist/providers/InstructionsProvider/useInstructions.js +21 -2
  61. package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
  62. package/dist/styles.d.ts +11 -0
  63. package/dist/types/handlebars-async-helpers.d.ts +1 -0
  64. package/dist/types/handlebars-dist-handlebars.d.ts +1 -0
  65. package/dist/types/react-mentions.d.ts +1 -0
  66. package/dist/types.d.ts +0 -3
  67. package/dist/types.js.map +1 -1
  68. package/dist/ui/AIConfigDashboard/index.js +198 -22
  69. package/dist/ui/AIConfigDashboard/index.js.map +1 -1
  70. package/dist/ui/AIConfigDashboard/index.jsx +159 -13
  71. package/dist/ui/Compose/Compose.d.ts +1 -0
  72. package/dist/ui/Compose/Compose.js +7 -5
  73. package/dist/ui/Compose/Compose.js.map +1 -1
  74. package/dist/ui/Compose/Compose.jsx +5 -5
  75. package/dist/ui/Compose/UndoRedoActions.d.ts +2 -2
  76. package/dist/ui/Compose/UndoRedoActions.js +8 -5
  77. package/dist/ui/Compose/UndoRedoActions.js.map +1 -1
  78. package/dist/ui/Compose/UndoRedoActions.jsx +6 -5
  79. package/dist/ui/Compose/compose.module.css +56 -16
  80. package/dist/ui/Compose/hooks/menu/itemsMap.js +12 -6
  81. package/dist/ui/Compose/hooks/menu/itemsMap.js.map +1 -1
  82. package/dist/ui/Compose/hooks/menu/useMenu.js +26 -15
  83. package/dist/ui/Compose/hooks/menu/useMenu.js.map +1 -1
  84. package/dist/ui/Compose/hooks/menu/useMenu.jsx +25 -12
  85. package/dist/ui/Compose/hooks/useGenerate.js +34 -131
  86. package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
  87. package/dist/ui/Compose/hooks/useGenerateUpload.d.ts +11 -0
  88. package/dist/ui/Compose/hooks/useGenerateUpload.js +123 -0
  89. package/dist/ui/Compose/hooks/useGenerateUpload.js.map +1 -0
  90. package/dist/ui/Compose/hooks/useHistory.d.ts +0 -1
  91. package/dist/ui/Compose/hooks/useHistory.js +65 -25
  92. package/dist/ui/Compose/hooks/useHistory.js.map +1 -1
  93. package/dist/ui/Compose/hooks/useStreamingUpdate.d.ts +8 -0
  94. package/dist/ui/Compose/hooks/useStreamingUpdate.js +48 -0
  95. package/dist/ui/Compose/hooks/useStreamingUpdate.js.map +1 -0
  96. package/dist/ui/DynamicVoiceSelect/index.js +63 -11
  97. package/dist/ui/DynamicVoiceSelect/index.js.map +1 -1
  98. package/dist/ui/DynamicVoiceSelect/index.jsx +47 -14
  99. package/dist/ui/EncryptedTextField/index.js +4 -4
  100. package/dist/ui/EncryptedTextField/index.js.map +1 -1
  101. package/dist/ui/EncryptedTextField/index.jsx +4 -4
  102. package/dist/ui/VoicesFetcher/index.js +54 -8
  103. package/dist/ui/VoicesFetcher/index.js.map +1 -1
  104. package/dist/ui/VoicesFetcher/index.jsx +32 -9
  105. package/dist/utilities/buildSmartPrompt.d.ts +22 -0
  106. package/dist/utilities/buildSmartPrompt.js +143 -0
  107. package/dist/utilities/buildSmartPrompt.js.map +1 -0
  108. package/dist/utilities/resolveImageReferences.d.ts +3 -1
  109. package/dist/utilities/resolveImageReferences.js +21 -2
  110. package/dist/utilities/resolveImageReferences.js.map +1 -1
  111. package/dist/utilities/seedProperties.d.ts +7 -0
  112. package/dist/utilities/seedProperties.js +117 -0
  113. package/dist/utilities/seedProperties.js.map +1 -0
  114. package/dist/utilities/setSafeLexicalState.js +80 -6
  115. package/dist/utilities/setSafeLexicalState.js.map +1 -1
  116. package/dist/utilities/updateFieldsConfig.d.ts +1 -1
  117. package/dist/utilities/updateFieldsConfig.js +8 -1
  118. package/dist/utilities/updateFieldsConfig.js.map +1 -1
  119. package/package.json +4 -3
  120. package/dist/endpoints/chat.d.js +0 -3
  121. package/dist/endpoints/chat.d.js.map +0 -1
  122. package/dist/init.d.ts +0 -7
  123. package/dist/init.js +0 -135
  124. package/dist/init.js.map +0 -1
  125. package/dist/payload-ai.d.js +0 -3
  126. package/dist/payload-ai.d.js.map +0 -1
  127. package/dist/styles.d.js +0 -2
  128. package/dist/styles.d.js.map +0 -1
  129. package/dist/types/handlebars-async-helpers.d.js +0 -2
  130. package/dist/types/handlebars-async-helpers.d.js.map +0 -1
  131. package/dist/types/handlebars-dist-handlebars.d.js +0 -2
  132. package/dist/types/handlebars-dist-handlebars.d.js.map +0 -1
  133. package/dist/types/react-mentions.d.js +0 -2
  134. package/dist/types/react-mentions.d.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Button, useForm } from '@payloadcms/ui';
3
+ import { Button, useForm, useFormFields } from '@payloadcms/ui';
4
4
  import React, { useCallback, useState } from 'react';
5
5
  import { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js';
6
6
  /**
@@ -14,6 +14,9 @@ import { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js';
14
14
  const fieldPath = path || '';
15
15
  const blockPath = fieldPath.split('.').slice(0, -1).join('.');
16
16
  const voicesPath = `${blockPath}.voices`;
17
+ // Get the current voices array to know how many rows to remove
18
+ const voicesField = useFormFields(([fields])=>fields[voicesPath]);
19
+ const currentRowCount = voicesField && 'rows' in voicesField && Array.isArray(voicesField.rows) ? voicesField.rows.length : 0;
17
20
  const fetchVoices = useCallback(async ()=>{
18
21
  setLoading(true);
19
22
  try {
@@ -29,19 +32,62 @@ import { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js';
29
32
  throw new Error(error.message || 'Failed to fetch voices');
30
33
  }
31
34
  const data = await response.json();
32
- // Update the voices field with fetched voices
33
- dispatchFields({
34
- type: 'UPDATE',
35
- path: voicesPath,
36
- value: data.voices || []
37
- });
38
- alert(`Successfully fetched ${data.voices?.length || 0} voices!`);
35
+ const voices = data.voices || [];
36
+ // Remove existing rows first (in reverse order to maintain indices)
37
+ for(let i = currentRowCount - 1; i >= 0; i--){
38
+ dispatchFields({
39
+ type: 'REMOVE_ROW',
40
+ path: voicesPath,
41
+ rowIndex: i
42
+ });
43
+ }
44
+ // Add new rows for each voice using ADD_ROW action
45
+ for (const voice of voices){
46
+ dispatchFields({
47
+ type: 'ADD_ROW',
48
+ path: voicesPath,
49
+ subFieldState: {
50
+ id: {
51
+ initialValue: voice.id,
52
+ valid: true,
53
+ value: voice.id
54
+ },
55
+ name: {
56
+ initialValue: voice.name,
57
+ valid: true,
58
+ value: voice.name
59
+ },
60
+ category: {
61
+ initialValue: voice.category || 'premade',
62
+ valid: true,
63
+ value: voice.category || 'premade'
64
+ },
65
+ enabled: {
66
+ initialValue: voice.enabled !== false,
67
+ valid: true,
68
+ value: voice.enabled !== false
69
+ },
70
+ labels: {
71
+ initialValue: voice.labels || {},
72
+ valid: true,
73
+ value: voice.labels || {}
74
+ },
75
+ preview_url: {
76
+ initialValue: voice.preview_url || '',
77
+ valid: true,
78
+ value: voice.preview_url || ''
79
+ }
80
+ }
81
+ });
82
+ }
83
+ alert(`Successfully fetched ${voices.length} voices!`);
39
84
  } catch (error) {
40
85
  alert(`Error: ${error instanceof Error ? error.message : 'Failed to fetch voices'}`);
41
86
  } finally{
42
87
  setLoading(false);
43
88
  }
44
89
  }, [
90
+ currentRowCount,
45
91
  dispatchFields,
46
92
  voicesPath
47
93
  ]);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/VoicesFetcher/index.tsx"],"sourcesContent":["'use client'\n\nimport type { FieldClientComponent } from 'payload'\n\nimport { Button, useForm } from '@payloadcms/ui'\nimport React, { useCallback, useState } from 'react'\n\nimport { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js'\n\n/**\n * VoicesFetcher Component\n * Fetches voices from ElevenLabs API (server-side) and populates the voices array field\n * SECURE: API key is never exposed to the client\n */\nexport const VoicesFetcher: FieldClientComponent = ({ path }) => {\n const [loading, setLoading] = useState(false)\n const { dispatchFields } = useForm()\n\n // Get the parent path (the block path)\n const fieldPath = (path as string) || ''\n const blockPath = fieldPath.split('.').slice(0, -1).join('.')\n const voicesPath = `${blockPath}.voices`\n\n const fetchVoices = useCallback(async () => {\n setLoading(true)\n\n try {\n // Call server endpoint - it will read the API key from the database\n const response = await fetch(`/api${PLUGIN_API_ENDPOINT_FETCH_VOICES}`, {\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(error.message || 'Failed to fetch voices')\n }\n\n const data = await response.json()\n\n // Update the voices field with fetched voices\n dispatchFields({\n type: 'UPDATE',\n path: voicesPath,\n value: data.voices || [],\n })\n\n alert(`Successfully fetched ${data.voices?.length || 0} voices!`)\n } catch (error) {\n alert(`Error: ${error instanceof Error ? error.message : 'Failed to fetch voices'}`)\n } finally {\n setLoading(false)\n }\n }, [dispatchFields, voicesPath])\n\n return (\n <div style={{ marginBottom: '20px' }}>\n <Button buttonStyle=\"secondary\" disabled={loading} onClick={fetchVoices} size=\"medium\">\n {loading ? 'Fetching Voices...' : 'Fetch Voices from ElevenLabs'}\n </Button>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px', marginTop: '8px' }}>\n This will fetch all available voices from your ElevenLabs account. Make sure you have saved\n your API key in the Setup tab first.\n </p>\n </div>\n )\n}\n"],"names":["Button","useForm","React","useCallback","useState","PLUGIN_API_ENDPOINT_FETCH_VOICES","VoicesFetcher","path","loading","setLoading","dispatchFields","fieldPath","blockPath","split","slice","join","voicesPath","fetchVoices","response","fetch","headers","method","ok","error","json","Error","message","data","type","value","voices","alert","length","div","style","marginBottom","buttonStyle","disabled","onClick","size","p","color","fontSize","marginTop"],"mappings":"AAAA;;AAIA,SAASA,MAAM,EAAEC,OAAO,QAAQ,iBAAgB;AAChD,OAAOC,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAEpD,SAASC,gCAAgC,QAAQ,oBAAmB;AAEpE;;;;CAIC,GACD,OAAO,MAAMC,gBAAsC,CAAC,EAAEC,IAAI,EAAE;IAC1D,MAAM,CAACC,SAASC,WAAW,GAAGL,SAAS;IACvC,MAAM,EAAEM,cAAc,EAAE,GAAGT;IAE3B,uCAAuC;IACvC,MAAMU,YAAY,AAACJ,QAAmB;IACtC,MAAMK,YAAYD,UAAUE,KAAK,CAAC,KAAKC,KAAK,CAAC,GAAG,CAAC,GAAGC,IAAI,CAAC;IACzD,MAAMC,aAAa,CAAC,EAAEJ,UAAU,OAAO,CAAC;IAExC,MAAMK,cAAcd,YAAY;QAC9BM,WAAW;QAEX,IAAI;YACF,oEAAoE;YACpE,MAAMS,WAAW,MAAMC,MAAM,CAAC,IAAI,EAAEd,iCAAiC,CAAC,EAAE;gBACtEe,SAAS;oBACP,gBAAgB;gBAClB;gBACAC,QAAQ;YACV;YAEA,IAAI,CAACH,SAASI,EAAE,EAAE;gBAChB,MAAMC,QAAQ,MAAML,SAASM,IAAI;gBACjC,MAAM,IAAIC,MAAMF,MAAMG,OAAO,IAAI;YACnC;YAEA,MAAMC,OAAO,MAAMT,SAASM,IAAI;YAEhC,8CAA8C;YAC9Cd,eAAe;gBACbkB,MAAM;gBACNrB,MAAMS;gBACNa,OAAOF,KAAKG,MAAM,IAAI,EAAE;YAC1B;YAEAC,MAAM,CAAC,qBAAqB,EAAEJ,KAAKG,MAAM,EAAEE,UAAU,EAAE,QAAQ,CAAC;QAClE,EAAE,OAAOT,OAAO;YACdQ,MAAM,CAAC,OAAO,EAAER,iBAAiBE,QAAQF,MAAMG,OAAO,GAAG,yBAAyB,CAAC;QACrF,SAAU;YACRjB,WAAW;QACb;IACF,GAAG;QAACC;QAAgBM;KAAW;IAE/B,qBACE,MAACiB;QAAIC,OAAO;YAAEC,cAAc;QAAO;;0BACjC,KAACnC;gBAAOoC,aAAY;gBAAYC,UAAU7B;gBAAS8B,SAASrB;gBAAasB,MAAK;0BAC3E/B,UAAU,uBAAuB;;0BAEpC,KAACgC;gBAAEN,OAAO;oBAAEO,OAAO;oBAA8BC,UAAU;oBAAQC,WAAW;gBAAM;0BAAG;;;;AAM7F,EAAC"}
1
+ {"version":3,"sources":["../../../src/ui/VoicesFetcher/index.tsx"],"sourcesContent":["'use client'\n\nimport type { FieldClientComponent } from 'payload'\n\nimport { Button, useForm, useFormFields } from '@payloadcms/ui'\nimport React, { useCallback, useState } from 'react'\n\nimport { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js'\n\ninterface Voice {\n category?: string\n enabled?: boolean\n id: string\n labels?: Record<string, unknown>\n name: string\n preview_url?: string\n}\n\n/**\n * VoicesFetcher Component\n * Fetches voices from ElevenLabs API (server-side) and populates the voices array field\n * SECURE: API key is never exposed to the client\n */\nexport const VoicesFetcher: FieldClientComponent = ({ path }) => {\n const [loading, setLoading] = useState(false)\n const { dispatchFields } = useForm()\n\n // Get the parent path (the block path)\n const fieldPath = (path as string) || ''\n const blockPath = fieldPath.split('.').slice(0, -1).join('.')\n const voicesPath = `${blockPath}.voices`\n\n // Get the current voices array to know how many rows to remove\n const voicesField = useFormFields(([fields]) => fields[voicesPath])\n const currentRowCount =\n voicesField && 'rows' in voicesField && Array.isArray(voicesField.rows)\n ? voicesField.rows.length\n : 0\n\n const fetchVoices = useCallback(async () => {\n setLoading(true)\n\n try {\n // Call server endpoint - it will read the API key from the database\n const response = await fetch(`/api${PLUGIN_API_ENDPOINT_FETCH_VOICES}`, {\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(error.message || 'Failed to fetch voices')\n }\n\n const data = await response.json()\n const voices: Voice[] = data.voices || []\n\n // Remove existing rows first (in reverse order to maintain indices)\n for (let i = currentRowCount - 1; i >= 0; i--) {\n dispatchFields({\n type: 'REMOVE_ROW',\n path: voicesPath,\n rowIndex: i,\n })\n }\n\n // Add new rows for each voice using ADD_ROW action\n for (const voice of voices) {\n dispatchFields({\n type: 'ADD_ROW',\n path: voicesPath,\n subFieldState: {\n id: { initialValue: voice.id, valid: true, value: voice.id },\n name: { initialValue: voice.name, valid: true, value: voice.name },\n category: { initialValue: voice.category || 'premade', valid: true, value: voice.category || 'premade' },\n enabled: { initialValue: voice.enabled !== false, valid: true, value: voice.enabled !== false },\n labels: { initialValue: voice.labels || {}, valid: true, value: voice.labels || {} },\n preview_url: { initialValue: voice.preview_url || '', valid: true, value: voice.preview_url || '' },\n },\n })\n }\n\n alert(`Successfully fetched ${voices.length} voices!`)\n } catch (error) {\n alert(`Error: ${error instanceof Error ? error.message : 'Failed to fetch voices'}`)\n } finally {\n setLoading(false)\n }\n }, [currentRowCount, dispatchFields, voicesPath])\n\n return (\n <div style={{ marginBottom: '20px' }}>\n <Button buttonStyle=\"secondary\" disabled={loading} onClick={fetchVoices} size=\"medium\">\n {loading ? 'Fetching Voices...' : 'Fetch Voices from ElevenLabs'}\n </Button>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px', marginTop: '8px' }}>\n This will fetch all available voices from your ElevenLabs account. Make sure you have saved\n your API key in the Setup tab first.\n </p>\n </div>\n )\n}\n\n\n\n"],"names":["Button","useForm","useFormFields","React","useCallback","useState","PLUGIN_API_ENDPOINT_FETCH_VOICES","VoicesFetcher","path","loading","setLoading","dispatchFields","fieldPath","blockPath","split","slice","join","voicesPath","voicesField","fields","currentRowCount","Array","isArray","rows","length","fetchVoices","response","fetch","headers","method","ok","error","json","Error","message","data","voices","i","type","rowIndex","voice","subFieldState","id","initialValue","valid","value","name","category","enabled","labels","preview_url","alert","div","style","marginBottom","buttonStyle","disabled","onClick","size","p","color","fontSize","marginTop"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { Button, useForm } from '@payloadcms/ui';
2
+ import { Button, useForm, useFormFields } from '@payloadcms/ui';
3
3
  import React, { useCallback, useState } from 'react';
4
4
  import { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js';
5
5
  /**
@@ -14,6 +14,11 @@ export const VoicesFetcher = ({ path }) => {
14
14
  const fieldPath = path || '';
15
15
  const blockPath = fieldPath.split('.').slice(0, -1).join('.');
16
16
  const voicesPath = `${blockPath}.voices`;
17
+ // Get the current voices array to know how many rows to remove
18
+ const voicesField = useFormFields(([fields]) => fields[voicesPath]);
19
+ const currentRowCount = voicesField && 'rows' in voicesField && Array.isArray(voicesField.rows)
20
+ ? voicesField.rows.length
21
+ : 0;
17
22
  const fetchVoices = useCallback(async () => {
18
23
  setLoading(true);
19
24
  try {
@@ -29,13 +34,31 @@ export const VoicesFetcher = ({ path }) => {
29
34
  throw new Error(error.message || 'Failed to fetch voices');
30
35
  }
31
36
  const data = await response.json();
32
- // Update the voices field with fetched voices
33
- dispatchFields({
34
- type: 'UPDATE',
35
- path: voicesPath,
36
- value: data.voices || [],
37
- });
38
- alert(`Successfully fetched ${data.voices?.length || 0} voices!`);
37
+ const voices = data.voices || [];
38
+ // Remove existing rows first (in reverse order to maintain indices)
39
+ for (let i = currentRowCount - 1; i >= 0; i--) {
40
+ dispatchFields({
41
+ type: 'REMOVE_ROW',
42
+ path: voicesPath,
43
+ rowIndex: i,
44
+ });
45
+ }
46
+ // Add new rows for each voice using ADD_ROW action
47
+ for (const voice of voices) {
48
+ dispatchFields({
49
+ type: 'ADD_ROW',
50
+ path: voicesPath,
51
+ subFieldState: {
52
+ id: { initialValue: voice.id, valid: true, value: voice.id },
53
+ name: { initialValue: voice.name, valid: true, value: voice.name },
54
+ category: { initialValue: voice.category || 'premade', valid: true, value: voice.category || 'premade' },
55
+ enabled: { initialValue: voice.enabled !== false, valid: true, value: voice.enabled !== false },
56
+ labels: { initialValue: voice.labels || {}, valid: true, value: voice.labels || {} },
57
+ preview_url: { initialValue: voice.preview_url || '', valid: true, value: voice.preview_url || '' },
58
+ },
59
+ });
60
+ }
61
+ alert(`Successfully fetched ${voices.length} voices!`);
39
62
  }
40
63
  catch (error) {
41
64
  alert(`Error: ${error instanceof Error ? error.message : 'Failed to fetch voices'}`);
@@ -43,7 +66,7 @@ export const VoicesFetcher = ({ path }) => {
43
66
  finally {
44
67
  setLoading(false);
45
68
  }
46
- }, [dispatchFields, voicesPath]);
69
+ }, [currentRowCount, dispatchFields, voicesPath]);
47
70
  return (<div style={{ marginBottom: '20px' }}>
48
71
  <Button buttonStyle="secondary" disabled={loading} onClick={fetchVoices} size="medium">
49
72
  {loading ? 'Fetching Voices...' : 'Fetch Voices from ElevenLabs'}
@@ -0,0 +1,22 @@
1
+ import type { Payload } from 'payload';
2
+ export interface SmartPromptContext {
3
+ /** The document data for template interpolation */
4
+ documentData?: Record<string, unknown>;
5
+ /** The Payload instance to access collection config */
6
+ payload: Payload;
7
+ /** The schema path like 'array-test-cases.teamMembers.contact.email' */
8
+ schemaPath: string;
9
+ }
10
+ /**
11
+ * Build a smart contextual prompt based on field metadata.
12
+ * This is used as a fallback when the user hasn't set a custom prompt.
13
+ *
14
+ * @param context - The context containing schema path and document data
15
+ * @returns A contextual prompt string that can be used for AI generation
16
+ */
17
+ export declare const buildSmartPrompt: (context: SmartPromptContext) => string;
18
+ /**
19
+ * Check if a prompt template is empty and should be replaced with a smart prompt.
20
+ * Only triggers when the prompt is completely empty or whitespace-only.
21
+ */
22
+ export declare const isGenericPrompt: (template: null | string | undefined) => boolean;
@@ -0,0 +1,143 @@
1
+ 'use strict';
2
+ import { getFieldBySchemaPath } from './getFieldBySchemaPath.js';
3
+ /**
4
+ * Extract field information from a schema path
5
+ */ const getFieldInfo = (schemaPath, payload)=>{
6
+ const parts = schemaPath.split('.');
7
+ const collectionSlug = parts[0];
8
+ const fieldPath = parts.slice(1);
9
+ const fieldName = fieldPath[fieldPath.length - 1] || '';
10
+ // Get parent context (e.g., 'teamMembers' for 'teamMembers.name')
11
+ let parentContext = null;
12
+ if (fieldPath.length > 1) {
13
+ parentContext = fieldPath[fieldPath.length - 2];
14
+ }
15
+ // Try to get the actual field configuration from the collection
16
+ let field = null;
17
+ const collection = payload.config.collections.find((c)=>c.slug === collectionSlug);
18
+ if (collection) {
19
+ field = getFieldBySchemaPath(collection, schemaPath);
20
+ }
21
+ return {
22
+ name: fieldName,
23
+ type: field?.type || 'text',
24
+ field,
25
+ label: field?.label || fieldName,
26
+ parentContext
27
+ };
28
+ };
29
+ /**
30
+ * Humanize a camelCase or snake_case field name
31
+ * e.g., 'teamMembers' -> 'team members', 'first_name' -> 'first name'
32
+ */ const humanize = (str)=>{
33
+ return str.replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase to spaces
34
+ .replace(/[_-]/g, ' ') // underscores/dashes to spaces
35
+ .toLowerCase().trim();
36
+ };
37
+ /**
38
+ * Get a description snippet from field admin config
39
+ */ const getFieldDescription = (field)=>{
40
+ if (!field) {
41
+ return null;
42
+ }
43
+ const admin = field.admin;
44
+ if (admin?.description && typeof admin.description === 'string') {
45
+ return admin.description;
46
+ }
47
+ return null;
48
+ };
49
+ /**
50
+ * Build type-specific prompt guidance
51
+ */ const getTypeGuidance = (type, fieldName)=>{
52
+ const nameHint = humanize(fieldName);
53
+ switch(type){
54
+ case 'code':
55
+ return `Generate code for ${nameHint}`;
56
+ case 'date':
57
+ return `Generate an appropriate date for ${nameHint}`;
58
+ case 'email':
59
+ return `Generate a valid professional email address`;
60
+ case 'json':
61
+ return `Generate valid JSON data for ${nameHint}`;
62
+ case 'number':
63
+ return `Generate an appropriate numeric value for ${nameHint}`;
64
+ case 'select':
65
+ return `Select an appropriate option for ${nameHint}`;
66
+ case 'text':
67
+ return `Generate appropriate text for ${nameHint}`;
68
+ case 'textarea':
69
+ return `Write detailed content for ${nameHint}`;
70
+ case 'upload':
71
+ // Explicit image generation instruction for multimodal models
72
+ return `Generate an image of ${nameHint}`;
73
+ default:
74
+ return `Generate content for ${nameHint}`;
75
+ }
76
+ };
77
+ /**
78
+ * Build context from parent field name using generic humanization.
79
+ * Works universally for any collection structure.
80
+ */ const getParentContextPhrase = (parentContext)=>{
81
+ if (!parentContext) {
82
+ return '';
83
+ }
84
+ const humanized = humanize(parentContext);
85
+ // Use singular form if the name ends with 's' (common for arrays)
86
+ // e.g., "teamMembers" → "team member", "products" → "product"
87
+ if (humanized.endsWith('s') && humanized.length > 2) {
88
+ return `for a ${humanized.slice(0, -1)} entry`;
89
+ }
90
+ return `for ${humanized}`;
91
+ };
92
+ /**
93
+ * Build a smart contextual prompt based on field metadata.
94
+ * This is used as a fallback when the user hasn't set a custom prompt.
95
+ *
96
+ * @param context - The context containing schema path and document data
97
+ * @returns A contextual prompt string that can be used for AI generation
98
+ */ export const buildSmartPrompt = (context)=>{
99
+ const { documentData, payload, schemaPath } = context;
100
+ const fieldInfo = getFieldInfo(schemaPath, payload);
101
+ const { name, type, field, label, parentContext } = fieldInfo;
102
+ // Start with the field's own description if available
103
+ const description = getFieldDescription(field);
104
+ // Build the prompt components
105
+ const parts = [];
106
+ // Use description as primary guidance if available
107
+ if (description) {
108
+ parts.push(description);
109
+ } else {
110
+ // Fall back to type-based guidance, prefer label over name for better context
111
+ parts.push(getTypeGuidance(type, label || name));
112
+ }
113
+ // Add parent context if nested
114
+ const parentPhrase = getParentContextPhrase(parentContext);
115
+ if (parentPhrase) {
116
+ parts.push(parentPhrase);
117
+ }
118
+ // Add document title context if available
119
+ const title = documentData?.title;
120
+ if (title && typeof title === 'string') {
121
+ parts.push(`in the context of "${title}"`);
122
+ }
123
+ // Build the final prompt
124
+ let prompt = parts.join(' ');
125
+ // Ensure first letter is capitalized
126
+ prompt = prompt.charAt(0).toUpperCase() + prompt.slice(1);
127
+ // Add instruction suffix for clarity
128
+ if (!prompt.endsWith('.')) {
129
+ prompt += '.';
130
+ }
131
+ return prompt;
132
+ };
133
+ /**
134
+ * Check if a prompt template is empty and should be replaced with a smart prompt.
135
+ * Only triggers when the prompt is completely empty or whitespace-only.
136
+ */ export const isGenericPrompt = (template)=>{
137
+ if (!template) {
138
+ return true;
139
+ }
140
+ return template.trim() === '';
141
+ };
142
+
143
+ //# sourceMappingURL=buildSmartPrompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/buildSmartPrompt.ts"],"sourcesContent":["'use strict'\n\nimport type { Field, Payload } from 'payload'\n\nimport { getFieldBySchemaPath } from './getFieldBySchemaPath.js'\n\nexport interface SmartPromptContext {\n /** The document data for template interpolation */\n documentData?: Record<string, unknown>\n /** The Payload instance to access collection config */\n payload: Payload\n /** The schema path like 'array-test-cases.teamMembers.contact.email' */\n schemaPath: string\n}\n\ninterface FieldInfo {\n /** The field configuration */\n field: Field | null\n /** Human-readable field label */\n label: string\n /** Field name from the path */\n name: string\n /** Parent field name if nested (e.g., 'teamMembers' for 'teamMembers.name') */\n parentContext: null | string\n /** The field type */\n type: string\n}\n\n/**\n * Extract field information from a schema path\n */\nconst getFieldInfo = (schemaPath: string, payload: Payload): FieldInfo => {\n const parts = schemaPath.split('.')\n const collectionSlug = parts[0]\n const fieldPath = parts.slice(1)\n const fieldName = fieldPath[fieldPath.length - 1] || ''\n \n // Get parent context (e.g., 'teamMembers' for 'teamMembers.name')\n let parentContext: null | string = null\n if (fieldPath.length > 1) {\n parentContext = fieldPath[fieldPath.length - 2]\n }\n \n // Try to get the actual field configuration from the collection\n let field: Field | null = null\n const collection = payload.config.collections.find(c => c.slug === collectionSlug)\n if (collection) {\n field = getFieldBySchemaPath(collection, schemaPath)\n }\n \n return {\n name: fieldName,\n type: field?.type || 'text',\n field,\n label: (field as { label?: string })?.label || fieldName,\n parentContext,\n }\n}\n\n/**\n * Humanize a camelCase or snake_case field name\n * e.g., 'teamMembers' -> 'team members', 'first_name' -> 'first name'\n */\nconst humanize = (str: string): string => {\n return str\n .replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase to spaces\n .replace(/[_-]/g, ' ') // underscores/dashes to spaces\n .toLowerCase()\n .trim()\n}\n\n/**\n * Get a description snippet from field admin config\n */\nconst getFieldDescription = (field: Field | null): null | string => {\n if (!field) {return null}\n const admin = (field as { admin?: { description?: string } }).admin\n if (admin?.description && typeof admin.description === 'string') {\n return admin.description\n }\n return null\n}\n\n/**\n * Build type-specific prompt guidance\n */\nconst getTypeGuidance = (type: string, fieldName: string): string => {\n const nameHint = humanize(fieldName)\n \n switch (type) {\n case 'code':\n return `Generate code for ${nameHint}`\n case 'date':\n return `Generate an appropriate date for ${nameHint}`\n case 'email':\n return `Generate a valid professional email address`\n case 'json':\n return `Generate valid JSON data for ${nameHint}`\n case 'number':\n return `Generate an appropriate numeric value for ${nameHint}`\n case 'select':\n return `Select an appropriate option for ${nameHint}`\n case 'text':\n return `Generate appropriate text for ${nameHint}`\n case 'textarea':\n return `Write detailed content for ${nameHint}`\n case 'upload':\n // Explicit image generation instruction for multimodal models\n return `Generate an image of ${nameHint}`\n default:\n return `Generate content for ${nameHint}`\n }\n}\n\n/**\n * Build context from parent field name using generic humanization.\n * Works universally for any collection structure.\n */\nconst getParentContextPhrase = (parentContext: null | string): string => {\n if (!parentContext) {\n return ''\n }\n \n const humanized = humanize(parentContext)\n \n // Use singular form if the name ends with 's' (common for arrays)\n // e.g., \"teamMembers\" → \"team member\", \"products\" → \"product\"\n if (humanized.endsWith('s') && humanized.length > 2) {\n return `for a ${humanized.slice(0, -1)} entry`\n }\n \n return `for ${humanized}`\n}\n\n/**\n * Build a smart contextual prompt based on field metadata.\n * This is used as a fallback when the user hasn't set a custom prompt.\n * \n * @param context - The context containing schema path and document data\n * @returns A contextual prompt string that can be used for AI generation\n */\nexport const buildSmartPrompt = (context: SmartPromptContext): string => {\n const { documentData, payload, schemaPath } = context\n \n const fieldInfo = getFieldInfo(schemaPath, payload)\n const { name, type, field, label, parentContext } = fieldInfo\n \n // Start with the field's own description if available\n const description = getFieldDescription(field)\n \n // Build the prompt components\n const parts: string[] = []\n \n // Use description as primary guidance if available\n if (description) {\n parts.push(description)\n } else {\n // Fall back to type-based guidance, prefer label over name for better context\n parts.push(getTypeGuidance(type, label || name))\n }\n \n // Add parent context if nested\n const parentPhrase = getParentContextPhrase(parentContext)\n if (parentPhrase) {\n parts.push(parentPhrase)\n }\n \n // Add document title context if available\n const title = documentData?.title\n if (title && typeof title === 'string') {\n parts.push(`in the context of \"${title}\"`)\n }\n \n // Build the final prompt\n let prompt = parts.join(' ')\n \n // Ensure first letter is capitalized\n prompt = prompt.charAt(0).toUpperCase() + prompt.slice(1)\n \n // Add instruction suffix for clarity\n if (!prompt.endsWith('.')) {\n prompt += '.'\n }\n \n return prompt\n}\n\n/**\n * Check if a prompt template is empty and should be replaced with a smart prompt.\n * Only triggers when the prompt is completely empty or whitespace-only.\n */\nexport const isGenericPrompt = (template: null | string | undefined): boolean => {\n if (!template) {\n return true\n }\n return template.trim() === ''\n}\n"],"names":["getFieldBySchemaPath","getFieldInfo","schemaPath","payload","parts","split","collectionSlug","fieldPath","slice","fieldName","length","parentContext","field","collection","config","collections","find","c","slug","name","type","label","humanize","str","replace","toLowerCase","trim","getFieldDescription","admin","description","getTypeGuidance","nameHint","getParentContextPhrase","humanized","endsWith","buildSmartPrompt","context","documentData","fieldInfo","push","parentPhrase","title","prompt","join","charAt","toUpperCase","isGenericPrompt","template"],"mappings":"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"}
@@ -17,12 +17,14 @@ interface ResolveImageReferencesResult {
17
17
  *
18
18
  * Supports two formats:
19
19
  * - @fieldName - for single upload fields
20
+ * - @collection.fieldName - schema path format (collection prefix is stripped)
20
21
  * - @fieldName:filename.jpg - for specific images in hasMany fields
21
22
  *
22
23
  * @param prompt - The prompt text containing @field references
23
24
  * @param contextData - The document data to resolve field values from
24
25
  * @param req - Payload request object for fetching media
26
+ * @param collectionSlug - Optional collection slug to strip from schema path references
25
27
  * @returns Processed prompt with references removed and array of resolved images
26
28
  */
27
- export declare function resolveImageReferences(prompt: string, contextData: Record<string, unknown>, req: PayloadRequest): Promise<ResolveImageReferencesResult>;
29
+ export declare function resolveImageReferences(prompt: string, contextData: Record<string, unknown>, req: PayloadRequest, collectionSlug?: string): Promise<ResolveImageReferencesResult>;
28
30
  export {};
@@ -1,15 +1,28 @@
1
+ /**
2
+ * Retrieves a nested value from an object using dot-notation path.
3
+ * For example, getNestedValue(obj, 'a.b.c') returns obj.a.b.c
4
+ */ function getNestedValue(obj, path) {
5
+ return path.split('.').reduce((current, key)=>{
6
+ if (current && typeof current === 'object' && key in current) {
7
+ return current[key];
8
+ }
9
+ return undefined;
10
+ }, obj);
11
+ }
1
12
  /**
2
13
  * Parses and resolves image references in prompts.
3
14
  *
4
15
  * Supports two formats:
5
16
  * - @fieldName - for single upload fields
17
+ * - @collection.fieldName - schema path format (collection prefix is stripped)
6
18
  * - @fieldName:filename.jpg - for specific images in hasMany fields
7
19
  *
8
20
  * @param prompt - The prompt text containing @field references
9
21
  * @param contextData - The document data to resolve field values from
10
22
  * @param req - Payload request object for fetching media
23
+ * @param collectionSlug - Optional collection slug to strip from schema path references
11
24
  * @returns Processed prompt with references removed and array of resolved images
12
- */ export async function resolveImageReferences(prompt, contextData, req) {
25
+ */ export async function resolveImageReferences(prompt, contextData, req, collectionSlug) {
13
26
  // Pattern matches: @fieldName or @fieldName:filename.ext (filename can have spaces)
14
27
  // The filename part matches everything up to and including an image extension
15
28
  const pattern = /@([\w.]+)(?::(.+?\.(?:png|jpe?g|webp|gif)))?/gi;
@@ -34,7 +47,13 @@
34
47
  // Resolve each reference
35
48
  for (const ref of references){
36
49
  try {
37
- const fieldValue = contextData[ref.fieldName];
50
+ // Strip collection prefix from schema path if it matches the current collection
51
+ // e.g., "characters.ortho3d.frame" becomes "ortho3d.frame" when collectionSlug is "characters"
52
+ let fieldPath = ref.fieldName;
53
+ if (collectionSlug && fieldPath.startsWith(`${collectionSlug}.`)) {
54
+ fieldPath = fieldPath.slice(collectionSlug.length + 1);
55
+ }
56
+ const fieldValue = getNestedValue(contextData, fieldPath);
38
57
  if (!fieldValue) {
39
58
  req.payload.logger.warn(`Image reference @${ref.fieldName} not found in document context`);
40
59
  continue;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/resolveImageReferences.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\ninterface ImageReference {\n fieldName: string\n filename?: string\n fullMatch: string\n}\n\nexport interface ResolvedImage {\n image: {\n mimeType?: string\n name: string\n thumbnailURL?: string\n type: string\n url: string\n }\n}\n\ninterface ResolveImageReferencesResult {\n images: ResolvedImage[]\n processedPrompt: string\n}\n\n/**\n * Parses and resolves image references in prompts.\n * \n * Supports two formats:\n * - @fieldName - for single upload fields\n * - @fieldName:filename.jpg - for specific images in hasMany fields\n * \n * @param prompt - The prompt text containing @field references\n * @param contextData - The document data to resolve field values from\n * @param req - Payload request object for fetching media\n * @returns Processed prompt with references removed and array of resolved images\n */\nexport async function resolveImageReferences(\n prompt: string,\n contextData: Record<string, unknown>,\n req: PayloadRequest,\n): Promise<ResolveImageReferencesResult> {\n // Pattern matches: @fieldName or @fieldName:filename.ext (filename can have spaces)\n // The filename part matches everything up to and including an image extension\n const pattern = /@([\\w.]+)(?::(.+?\\.(?:png|jpe?g|webp|gif)))?/gi\n const references: ImageReference[] = []\n let match: null | RegExpExecArray\n\n // Extract all image references\n while ((match = pattern.exec(prompt)) !== null) {\n references.push({\n fieldName: match[1],\n filename: match[2],\n fullMatch: match[0],\n })\n }\n\n if (references.length === 0) {\n return { images: [], processedPrompt: prompt }\n }\n\n const resolvedImages: ResolvedImage[] = []\n let processedPrompt = prompt\n\n // Resolve each reference\n for (const ref of references) {\n try {\n const fieldValue = contextData[ref.fieldName]\n\n if (!fieldValue) {\n req.payload.logger.warn(\n `Image reference @${ref.fieldName} not found in document context`,\n )\n continue\n }\n\n // Handle single upload field (value is an ID or object)\n if (!ref.filename) {\n const mediaDoc = await resolveMediaDocument(fieldValue, req)\n if (mediaDoc) {\n resolvedImages.push(formatImageData(mediaDoc))\n }\n }\n // Handle hasMany field with filename\n else {\n const mediaDoc = await resolveMediaFromArray(fieldValue, ref.filename, req)\n if (mediaDoc) {\n resolvedImages.push(formatImageData(mediaDoc))\n }\n }\n\n // Remove the reference from the prompt\n processedPrompt = processedPrompt.replace(ref.fullMatch, '')\n } catch (error) {\n req.payload.logger.error(\n error,\n `Error resolving image reference: ${ref.fullMatch}`,\n )\n }\n }\n\n // Clean up extra whitespace from removed references\n processedPrompt = processedPrompt.replace(/\\s+/g, ' ').trim()\n\n return {\n images: resolvedImages,\n processedPrompt,\n }\n}\n\n/**\n * Resolves a single media document from an ID or populated object\n */\nasync function resolveMediaDocument(\n value: unknown,\n req: PayloadRequest,\n): Promise<null | Record<string, unknown>> {\n // If it's already a populated object with required fields\n if (typeof value === 'object' && value !== null && 'url' in value) {\n return value as Record<string, unknown>\n }\n\n // If it's an ID string, fetch the media document\n if (typeof value === 'string' || typeof value === 'number') {\n try {\n // Try to find which collection this media belongs to\n // First, check the common 'media' collection\n const collections = ['media', 'uploads']\n\n for (const collectionSlug of collections) {\n try {\n const mediaDoc = await req.payload.findByID({\n id: value,\n collection: collectionSlug,\n req,\n })\n if (mediaDoc) {\n return mediaDoc as Record<string, unknown>\n }\n } catch (_ignore) {\n // Continue to next collection\n continue\n }\n }\n } catch (error) {\n req.payload.logger.error(error, 'Error fetching media document')\n }\n }\n\n return null\n}\n\n/**\n * Resolves a specific media document from an array by matching filename\n */\nasync function resolveMediaFromArray(\n arrayValue: unknown,\n filename: string,\n req: PayloadRequest,\n): Promise<null | Record<string, unknown>> {\n if (!Array.isArray(arrayValue)) {\n return null\n }\n\n // Search through array for matching filename\n for (const item of arrayValue) {\n const mediaDoc = await resolveMediaDocument(item, req)\n\n if (mediaDoc && matchesFilename(mediaDoc, filename)) {\n return mediaDoc\n }\n }\n\n return null\n}\n\n/**\n * Checks if a media document matches the given filename\n */\nfunction matchesFilename(mediaDoc: Record<string, unknown>, filename: string): boolean {\n const docFilename = mediaDoc.filename || mediaDoc.name\n\n if (!docFilename) {\n return false\n }\n\n // Case-insensitive match\n return (docFilename as string).toLowerCase() === filename.toLowerCase()\n}\n\n/**\n * Formats media document into the expected image data structure\n */\nfunction formatImageData(mediaDoc: Record<string, unknown>): ResolvedImage {\n return {\n image: {\n name: (mediaDoc.filename || mediaDoc.name || 'unknown') as string,\n type: extractFileExtension((mediaDoc.filename || mediaDoc.name || '') as string),\n mimeType: (mediaDoc.mimeType || mediaDoc.mimetype) as string | undefined,\n thumbnailURL: mediaDoc.thumbnailURL as string | undefined,\n url: mediaDoc.url as string,\n },\n }\n}\n\n/**\n * Extracts file extension from filename\n */\nfunction extractFileExtension(filename: string): string {\n const match = filename.match(/\\.([^.]+)$/)\n return match ? match[1].toLowerCase() : 'unknown'\n}\n"],"names":["resolveImageReferences","prompt","contextData","req","pattern","references","match","exec","push","fieldName","filename","fullMatch","length","images","processedPrompt","resolvedImages","ref","fieldValue","payload","logger","warn","mediaDoc","resolveMediaDocument","formatImageData","resolveMediaFromArray","replace","error","trim","value","collections","collectionSlug","findByID","id","collection","_ignore","arrayValue","Array","isArray","item","matchesFilename","docFilename","name","toLowerCase","image","type","extractFileExtension","mimeType","mimetype","thumbnailURL","url"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/utilities/resolveImageReferences.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\ninterface ImageReference {\n fieldName: string\n filename?: string\n fullMatch: string\n}\n\nexport interface ResolvedImage {\n image: {\n mimeType?: string\n name: string\n thumbnailURL?: string\n type: string\n url: string\n }\n}\n\ninterface ResolveImageReferencesResult {\n images: ResolvedImage[]\n processedPrompt: string\n}\n\n/**\n * Retrieves a nested value from an object using dot-notation path.\n * For example, getNestedValue(obj, 'a.b.c') returns obj.a.b.c\n */\nfunction getNestedValue(obj: Record<string, unknown>, path: string): unknown {\n return path.split('.').reduce((current, key) => {\n if (current && typeof current === 'object' && key in current) {\n return (current as Record<string, unknown>)[key]\n }\n return undefined\n }, obj as unknown)\n}\n\n/**\n * Parses and resolves image references in prompts.\n * \n * Supports two formats:\n * - @fieldName - for single upload fields\n * - @collection.fieldName - schema path format (collection prefix is stripped)\n * - @fieldName:filename.jpg - for specific images in hasMany fields\n * \n * @param prompt - The prompt text containing @field references\n * @param contextData - The document data to resolve field values from\n * @param req - Payload request object for fetching media\n * @param collectionSlug - Optional collection slug to strip from schema path references\n * @returns Processed prompt with references removed and array of resolved images\n */\nexport async function resolveImageReferences(\n prompt: string,\n contextData: Record<string, unknown>,\n req: PayloadRequest,\n collectionSlug?: string,\n): Promise<ResolveImageReferencesResult> {\n // Pattern matches: @fieldName or @fieldName:filename.ext (filename can have spaces)\n // The filename part matches everything up to and including an image extension\n const pattern = /@([\\w.]+)(?::(.+?\\.(?:png|jpe?g|webp|gif)))?/gi\n const references: ImageReference[] = []\n let match: null | RegExpExecArray\n\n // Extract all image references\n while ((match = pattern.exec(prompt)) !== null) {\n references.push({\n fieldName: match[1],\n filename: match[2],\n fullMatch: match[0],\n })\n }\n\n if (references.length === 0) {\n return { images: [], processedPrompt: prompt }\n }\n\n const resolvedImages: ResolvedImage[] = []\n let processedPrompt = prompt\n\n // Resolve each reference\n for (const ref of references) {\n try {\n // Strip collection prefix from schema path if it matches the current collection\n // e.g., \"characters.ortho3d.frame\" becomes \"ortho3d.frame\" when collectionSlug is \"characters\"\n let fieldPath = ref.fieldName\n if (collectionSlug && fieldPath.startsWith(`${collectionSlug}.`)) {\n fieldPath = fieldPath.slice(collectionSlug.length + 1)\n }\n\n const fieldValue = getNestedValue(contextData, fieldPath)\n\n if (!fieldValue) {\n req.payload.logger.warn(\n `Image reference @${ref.fieldName} not found in document context`,\n )\n continue\n }\n\n // Handle single upload field (value is an ID or object)\n if (!ref.filename) {\n const mediaDoc = await resolveMediaDocument(fieldValue, req)\n if (mediaDoc) {\n resolvedImages.push(formatImageData(mediaDoc))\n }\n }\n // Handle hasMany field with filename\n else {\n const mediaDoc = await resolveMediaFromArray(fieldValue, ref.filename, req)\n if (mediaDoc) {\n resolvedImages.push(formatImageData(mediaDoc))\n }\n }\n\n // Remove the reference from the prompt\n processedPrompt = processedPrompt.replace(ref.fullMatch, '')\n } catch (error) {\n req.payload.logger.error(\n error,\n `Error resolving image reference: ${ref.fullMatch}`,\n )\n }\n }\n\n // Clean up extra whitespace from removed references\n processedPrompt = processedPrompt.replace(/\\s+/g, ' ').trim()\n\n return {\n images: resolvedImages,\n processedPrompt,\n }\n}\n\n/**\n * Resolves a single media document from an ID or populated object\n */\nasync function resolveMediaDocument(\n value: unknown,\n req: PayloadRequest,\n): Promise<null | Record<string, unknown>> {\n // If it's already a populated object with required fields\n if (typeof value === 'object' && value !== null && 'url' in value) {\n return value as Record<string, unknown>\n }\n\n // If it's an ID string, fetch the media document\n if (typeof value === 'string' || typeof value === 'number') {\n try {\n // Try to find which collection this media belongs to\n // First, check the common 'media' collection\n const collections = ['media', 'uploads']\n\n for (const collectionSlug of collections) {\n try {\n const mediaDoc = await req.payload.findByID({\n id: value,\n collection: collectionSlug,\n req,\n })\n if (mediaDoc) {\n return mediaDoc as Record<string, unknown>\n }\n } catch (_ignore) {\n // Continue to next collection\n continue\n }\n }\n } catch (error) {\n req.payload.logger.error(error, 'Error fetching media document')\n }\n }\n\n return null\n}\n\n/**\n * Resolves a specific media document from an array by matching filename\n */\nasync function resolveMediaFromArray(\n arrayValue: unknown,\n filename: string,\n req: PayloadRequest,\n): Promise<null | Record<string, unknown>> {\n if (!Array.isArray(arrayValue)) {\n return null\n }\n\n // Search through array for matching filename\n for (const item of arrayValue) {\n const mediaDoc = await resolveMediaDocument(item, req)\n\n if (mediaDoc && matchesFilename(mediaDoc, filename)) {\n return mediaDoc\n }\n }\n\n return null\n}\n\n/**\n * Checks if a media document matches the given filename\n */\nfunction matchesFilename(mediaDoc: Record<string, unknown>, filename: string): boolean {\n const docFilename = mediaDoc.filename || mediaDoc.name\n\n if (!docFilename) {\n return false\n }\n\n // Case-insensitive match\n return (docFilename as string).toLowerCase() === filename.toLowerCase()\n}\n\n/**\n * Formats media document into the expected image data structure\n */\nfunction formatImageData(mediaDoc: Record<string, unknown>): ResolvedImage {\n return {\n image: {\n name: (mediaDoc.filename || mediaDoc.name || 'unknown') as string,\n type: extractFileExtension((mediaDoc.filename || mediaDoc.name || '') as string),\n mimeType: (mediaDoc.mimeType || mediaDoc.mimetype) as string | undefined,\n thumbnailURL: mediaDoc.thumbnailURL as string | undefined,\n url: mediaDoc.url as string,\n },\n }\n}\n\n/**\n * Extracts file extension from filename\n */\nfunction extractFileExtension(filename: string): string {\n const match = filename.match(/\\.([^.]+)$/)\n return match ? match[1].toLowerCase() : 'unknown'\n}\n"],"names":["getNestedValue","obj","path","split","reduce","current","key","undefined","resolveImageReferences","prompt","contextData","req","collectionSlug","pattern","references","match","exec","push","fieldName","filename","fullMatch","length","images","processedPrompt","resolvedImages","ref","fieldPath","startsWith","slice","fieldValue","payload","logger","warn","mediaDoc","resolveMediaDocument","formatImageData","resolveMediaFromArray","replace","error","trim","value","collections","findByID","id","collection","_ignore","arrayValue","Array","isArray","item","matchesFilename","docFilename","name","toLowerCase","image","type","extractFileExtension","mimeType","mimetype","thumbnailURL","url"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ interface SeedPropertiesArgs {
3
+ enabledCollections: string[];
4
+ req: PayloadRequest;
5
+ }
6
+ export declare const seedProperties: ({ enabledCollections, req }: SeedPropertiesArgs) => Promise<void>;
7
+ export {};
@@ -0,0 +1,117 @@
1
+ import { defaultSeedPrompts } from '../ai/prompts.js';
2
+ import { PLUGIN_INSTRUCTIONS_TABLE } from '../defaults.js';
3
+ import { updateFieldsConfig } from './updateFieldsConfig.js';
4
+ export const seedProperties = async ({ enabledCollections, req })=>{
5
+ const { payload } = req;
6
+ if (!enabledCollections || enabledCollections.length === 0) {
7
+ return;
8
+ }
9
+ // Get all collections from payload config
10
+ const allCollections = payload.config.collections;
11
+ for (const collectionSlug of enabledCollections){
12
+ const collectionConfig = allCollections.find((c)=>c.slug === collectionSlug);
13
+ if (!collectionConfig) {
14
+ continue;
15
+ }
16
+ // Traverse the collection config effectively using updateFieldsConfig
17
+ // Use the side-effect of getting schemaPathMap from it
18
+ const { schemaPathMap } = updateFieldsConfig(collectionConfig);
19
+ for (const [schemaPath, fieldInfo] of Object.entries(schemaPathMap)){
20
+ const { type, custom, label, relationTo } = fieldInfo;
21
+ // Check if instruction already exists
22
+ const existingInstruction = await payload.find({
23
+ collection: PLUGIN_INSTRUCTIONS_TABLE,
24
+ depth: 0,
25
+ limit: 1,
26
+ overrideAccess: true,
27
+ where: {
28
+ 'schema-path': {
29
+ equals: schemaPath
30
+ }
31
+ }
32
+ });
33
+ if (existingInstruction.totalDocs > 0) {
34
+ // If developer has provided custom prompts in the schema, update the existing record
35
+ // but only if the values have actually changed.
36
+ if (custom?.ai?.prompt || custom?.ai?.system) {
37
+ const doc = existingInstruction.docs[0];
38
+ const currentPrompt = doc.prompt;
39
+ const currentSystem = doc.system;
40
+ let needsUpdate = false;
41
+ const updateData = {};
42
+ if (custom?.ai?.prompt && custom.ai.prompt !== currentPrompt) {
43
+ updateData.prompt = custom.ai.prompt;
44
+ needsUpdate = true;
45
+ }
46
+ if (custom?.ai?.system && custom.ai.system !== currentSystem) {
47
+ updateData.system = custom.ai.system;
48
+ needsUpdate = true;
49
+ }
50
+ if (needsUpdate) {
51
+ try {
52
+ await payload.update({
53
+ id: doc.id,
54
+ collection: PLUGIN_INSTRUCTIONS_TABLE,
55
+ data: updateData,
56
+ overrideAccess: true
57
+ });
58
+ } catch (error) {
59
+ payload.logger.error(`Failed to update instruction for ${schemaPath}: ${error}`);
60
+ }
61
+ }
62
+ }
63
+ continue;
64
+ }
65
+ // Generate seed prompts
66
+ const seeded = await defaultSeedPrompts({
67
+ fieldLabel: label,
68
+ fieldSchemaPaths: {},
69
+ fieldType: type,
70
+ path: schemaPath
71
+ });
72
+ if (!seeded || typeof seeded !== 'object') {
73
+ continue;
74
+ }
75
+ let prompt = 'prompt' in seeded ? seeded.prompt : '';
76
+ let system = 'system' in seeded ? seeded.system : '';
77
+ // Override with custom prompts if defined
78
+ if (custom?.ai?.prompt) {
79
+ prompt = custom.ai.prompt;
80
+ }
81
+ if (custom?.ai?.system) {
82
+ system = custom.ai.system;
83
+ }
84
+ // Determine model-id based on field type
85
+ let modelId = 'text';
86
+ if (type === 'richText') {
87
+ modelId = 'richtext';
88
+ }
89
+ if (type === 'upload') {
90
+ modelId = 'image';
91
+ } // defaulting to image generation for uploads
92
+ if (type === 'array') {
93
+ modelId = 'array';
94
+ }
95
+ // Create new instruction
96
+ try {
97
+ await payload.create({
98
+ collection: PLUGIN_INSTRUCTIONS_TABLE,
99
+ data: {
100
+ prompt,
101
+ system,
102
+ disabled: false,
103
+ 'field-type': type,
104
+ 'model-id': modelId,
105
+ 'relation-to': relationTo,
106
+ 'schema-path': schemaPath
107
+ },
108
+ overrideAccess: true
109
+ });
110
+ } catch (error) {
111
+ payload.logger.error(`Failed to seed instruction for ${schemaPath}: ${error}`);
112
+ }
113
+ }
114
+ }
115
+ };
116
+
117
+ //# sourceMappingURL=seedProperties.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utilities/seedProperties.ts"],"sourcesContent":["import type { PayloadRequest } from 'payload'\n\nimport { defaultSeedPrompts } from '../ai/prompts.js'\nimport { PLUGIN_INSTRUCTIONS_TABLE } from '../defaults.js'\nimport { updateFieldsConfig } from './updateFieldsConfig.js'\n\ninterface SeedPropertiesArgs {\n enabledCollections: string[]\n req: PayloadRequest\n}\n\nexport const seedProperties = async ({ enabledCollections, req }: SeedPropertiesArgs): Promise<void> => {\n const { payload } = req\n\n if (!enabledCollections || enabledCollections.length === 0) {\n return\n }\n\n // Get all collections from payload config\n const allCollections = payload.config.collections\n\n for (const collectionSlug of enabledCollections) {\n const collectionConfig = allCollections.find((c) => c.slug === collectionSlug)\n if (!collectionConfig) {\n continue\n }\n\n // Traverse the collection config effectively using updateFieldsConfig\n // Use the side-effect of getting schemaPathMap from it\n const { schemaPathMap } = updateFieldsConfig(collectionConfig)\n\n for (const [schemaPath, fieldInfo] of Object.entries(schemaPathMap)) {\n const { type, custom, label, relationTo } = fieldInfo as {\n custom?: any\n label: string\n relationTo?: string\n type: string\n }\n\n // Check if instruction already exists\n const existingInstruction = await payload.find({\n collection: PLUGIN_INSTRUCTIONS_TABLE,\n depth: 0,\n limit: 1,\n overrideAccess: true,\n where: {\n 'schema-path': {\n equals: schemaPath,\n },\n },\n })\n\n if (existingInstruction.totalDocs > 0) {\n // If developer has provided custom prompts in the schema, update the existing record\n // but only if the values have actually changed.\n if (custom?.ai?.prompt || custom?.ai?.system) {\n const doc = existingInstruction.docs[0] as any\n const currentPrompt = doc.prompt\n const currentSystem = doc.system\n\n let needsUpdate = false\n const updateData: any = {}\n\n if (custom?.ai?.prompt && custom.ai.prompt !== currentPrompt) {\n updateData.prompt = custom.ai.prompt\n needsUpdate = true\n }\n if (custom?.ai?.system && custom.ai.system !== currentSystem) {\n updateData.system = custom.ai.system\n needsUpdate = true\n }\n\n if (needsUpdate) {\n try {\n await payload.update({\n id: doc.id,\n collection: PLUGIN_INSTRUCTIONS_TABLE,\n data: updateData,\n overrideAccess: true,\n })\n } catch (error) {\n payload.logger.error(`Failed to update instruction for ${schemaPath}: ${error}`)\n }\n }\n }\n continue\n }\n\n // Generate seed prompts\n const seeded = await defaultSeedPrompts({\n fieldLabel: label,\n fieldSchemaPaths: {}, // We might not need the full map here for individual seeding\n fieldType: type,\n path: schemaPath,\n })\n\n if (!seeded || typeof seeded !== 'object') {\n continue\n }\n\n let prompt = 'prompt' in seeded ? seeded.prompt : ''\n let system = 'system' in seeded ? seeded.system : ''\n\n // Override with custom prompts if defined\n if (custom?.ai?.prompt) {\n prompt = custom.ai.prompt\n }\n if (custom?.ai?.system) {\n system = custom.ai.system\n }\n\n // Determine model-id based on field type\n let modelId = 'text'\n if (type === 'richText') {\n modelId = 'richtext'\n }\n if (type === 'upload') {\n modelId = 'image'\n } // defaulting to image generation for uploads\n if (type === 'array') {\n modelId = 'array'\n }\n\n // Create new instruction\n try {\n await payload.create({\n collection: PLUGIN_INSTRUCTIONS_TABLE,\n data: {\n prompt,\n system,\n disabled: false,\n 'field-type': type,\n 'model-id': modelId,\n 'relation-to': relationTo,\n 'schema-path': schemaPath,\n },\n overrideAccess: true,\n })\n } catch (error) {\n payload.logger.error(`Failed to seed instruction for ${schemaPath}: ${error}`)\n }\n }\n }\n}\n"],"names":["defaultSeedPrompts","PLUGIN_INSTRUCTIONS_TABLE","updateFieldsConfig","seedProperties","enabledCollections","req","payload","length","allCollections","config","collections","collectionSlug","collectionConfig","find","c","slug","schemaPathMap","schemaPath","fieldInfo","Object","entries","type","custom","label","relationTo","existingInstruction","collection","depth","limit","overrideAccess","where","equals","totalDocs","ai","prompt","system","doc","docs","currentPrompt","currentSystem","needsUpdate","updateData","update","id","data","error","logger","seeded","fieldLabel","fieldSchemaPaths","fieldType","path","modelId","create","disabled"],"mappings":"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"}