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

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 (53) hide show
  1. package/dist/ai/core/streamObject.js +3 -0
  2. package/dist/ai/core/streamObject.js.map +1 -1
  3. package/dist/ai/core/types.d.ts +3 -0
  4. package/dist/ai/core/types.js.map +1 -1
  5. package/dist/ai/prompts.d.ts +1 -2
  6. package/dist/ai/prompts.js +0 -110
  7. package/dist/ai/prompts.js.map +1 -1
  8. package/dist/ai/providers/registry.js +0 -1
  9. package/dist/ai/providers/registry.js.map +1 -1
  10. package/dist/ai/utils/filterEditorSchemaByNodes.d.ts +9 -0
  11. package/dist/ai/utils/filterEditorSchemaByNodes.js +30 -3
  12. package/dist/ai/utils/filterEditorSchemaByNodes.js.map +1 -1
  13. package/dist/ai/utils/nodeToSchemaMap.d.ts +22 -0
  14. package/dist/ai/utils/nodeToSchemaMap.js +72 -0
  15. package/dist/ai/utils/nodeToSchemaMap.js.map +1 -0
  16. package/dist/collections/AIJobs.js +1 -1
  17. package/dist/collections/AIJobs.js.map +1 -1
  18. package/dist/collections/AISettings.js +3 -3
  19. package/dist/collections/AISettings.js.map +1 -1
  20. package/dist/endpoints/fetchVoices.js +41 -24
  21. package/dist/endpoints/fetchVoices.js.map +1 -1
  22. package/dist/endpoints/index.js +108 -6
  23. package/dist/endpoints/index.js.map +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/payload-ai.d.ts +7 -4
  28. package/dist/plugin.js +1 -1
  29. package/dist/plugin.js.map +1 -1
  30. package/dist/types.d.ts +34 -2
  31. package/dist/types.js +1 -0
  32. package/dist/types.js.map +1 -1
  33. package/dist/ui/Compose/Compose.js +23 -6
  34. package/dist/ui/Compose/Compose.js.map +1 -1
  35. package/dist/ui/Compose/Compose.jsx +23 -4
  36. package/dist/ui/Compose/hooks/useGenerate.js +21 -72
  37. package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
  38. package/dist/ui/Compose/hooks/useGenerateUpload.d.ts +3 -3
  39. package/dist/ui/Compose/hooks/useGenerateUpload.js +37 -10
  40. package/dist/ui/Compose/hooks/useGenerateUpload.js.map +1 -1
  41. package/dist/ui/Compose/hooks/useStreamingUpdate.js.map +1 -1
  42. package/dist/ui/VoicesFetcher/index.js +33 -61
  43. package/dist/ui/VoicesFetcher/index.js.map +1 -1
  44. package/dist/ui/VoicesFetcher/index.jsx +31 -37
  45. package/dist/utilities/buildSmartPrompt.js +4 -6
  46. package/dist/utilities/buildSmartPrompt.js.map +1 -1
  47. package/dist/utilities/encryption.js +2 -1
  48. package/dist/utilities/encryption.js.map +1 -1
  49. package/dist/utilities/seedProperties.js +7 -24
  50. package/dist/utilities/seedProperties.js.map +1 -1
  51. package/dist/utilities/setSafeLexicalState.js +1 -2
  52. package/dist/utilities/setSafeLexicalState.js.map +1 -1
  53. package/package.json +1 -1
@@ -1,14 +1,19 @@
1
- import { toast, useConfig, useDocumentInfo, useForm, useLocale } from '@payloadcms/ui';
1
+ import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui';
2
2
  import { useCallback, useState } from 'react';
3
3
  import { PLUGIN_AI_JOBS_TABLE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD } from '../../../defaults.js';
4
+ import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js';
4
5
  import { useHistory } from './useHistory.js';
5
- export const useGenerateUpload = ({ instructionIdRef, setValue })=>{
6
+ export const useGenerateUpload = ({ instructionIdRef })=>{
6
7
  const { config } = useConfig();
7
8
  const { routes: { api }, serverURL } = config;
8
9
  const { id: documentId, collectionSlug } = useDocumentInfo();
9
10
  const localFromContext = useLocale();
10
11
  const { getData } = useForm();
11
12
  const { set: setHistory } = useHistory();
13
+ const { field, path: pathFromContext } = useFieldProps();
14
+ const { setValue } = useField({
15
+ path: pathFromContext ?? ''
16
+ });
12
17
  // Async job UI state
13
18
  const [jobStatus, setJobStatus] = useState(undefined);
14
19
  const [jobProgress, setJobProgress] = useState(0);
@@ -36,7 +41,6 @@ export const useGenerateUpload = ({ instructionIdRef, setValue })=>{
36
41
  const json = await uploadResponse.json();
37
42
  const { job, result } = json || {};
38
43
  if (result) {
39
- // Set the upload ID
40
44
  setValue(result?.id);
41
45
  setHistory(result?.id);
42
46
  // Show toast to prompt user to save
@@ -66,11 +70,34 @@ export const useGenerateUpload = ({ instructionIdRef, setValue })=>{
66
70
  setJobProgress(progress ?? 0);
67
71
  // When result present, set field and finish
68
72
  if (status === 'completed' && result_id) {
69
- // Force upload field to refetch by clearing then setting the ID
70
- setValue(null);
71
- setTimeout(()=>{
72
- setValue(result_id);
73
- }, 0);
73
+ let valueToSet = result_id;
74
+ // Attempt to fetch full document for immediate preview
75
+ if (field && 'relationTo' in field && typeof field.relationTo === 'string') {
76
+ let attempts = 0;
77
+ const maxAttempts = 3;
78
+ while(attempts < maxAttempts){
79
+ try {
80
+ const docRes = await fetch(`${serverURL}${api}/${field.relationTo}/${result_id}`, {
81
+ credentials: 'include'
82
+ });
83
+ if (docRes.ok) {
84
+ const doc = await docRes.json();
85
+ // Verify we have a URL for preview
86
+ if (doc && doc.url) {
87
+ valueToSet = doc;
88
+ break;
89
+ }
90
+ }
91
+ } catch (e) {
92
+ console.error('Failed to fetch generated document for preview:', e);
93
+ }
94
+ attempts++;
95
+ if (attempts < maxAttempts) {
96
+ await new Promise((resolve)=>setTimeout(resolve, 500));
97
+ }
98
+ }
99
+ }
100
+ setValue(valueToSet);
74
101
  setHistory(result_id);
75
102
  setIsJobActive(false);
76
103
  return;
@@ -80,7 +107,7 @@ export const useGenerateUpload = ({ instructionIdRef, setValue })=>{
80
107
  throw new Error('Video generation failed');
81
108
  }
82
109
  }
83
- } catch (e) {
110
+ } catch (_) {
84
111
  // silent retry
85
112
  }
86
113
  attempts += 1;
@@ -105,7 +132,7 @@ export const useGenerateUpload = ({ instructionIdRef, setValue })=>{
105
132
  getData,
106
133
  localFromContext?.code,
107
134
  instructionIdRef,
108
- setValue,
135
+ // setValue,
109
136
  documentId,
110
137
  collectionSlug,
111
138
  serverURL,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/Compose/hooks/useGenerateUpload.ts"],"sourcesContent":["import { toast, useConfig, useDocumentInfo, useForm, useLocale } from '@payloadcms/ui'\nimport { useCallback, useState } from 'react'\n\nimport type { GenerateTextarea } from '../../../types.js'\n\nimport { PLUGIN_AI_JOBS_TABLE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD } from '../../../defaults.js'\nimport { useHistory } from './useHistory.js'\n\ntype UseGenerateUploadParams = {\n instructionIdRef: React.MutableRefObject<string>\n setValue: (value: any) => void\n}\n\nexport const useGenerateUpload = ({ instructionIdRef, setValue }: UseGenerateUploadParams) => {\n const { config } = useConfig()\n const {\n routes: { api },\n serverURL,\n } = config\n const { id: documentId, collectionSlug } = useDocumentInfo()\n const localFromContext = useLocale()\n const { getData } = useForm()\n const { set: setHistory } = useHistory()\n\n // Async job UI state\n const [jobStatus, setJobStatus] = useState<string | undefined>(undefined)\n const [jobProgress, setJobProgress] = useState<number>(0)\n const [isJobActive, setIsJobActive] = useState<boolean>(false)\n\n const generateUpload = useCallback(async () => {\n const doc = getData()\n const currentInstructionId = instructionIdRef.current\n\n return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {\n body: JSON.stringify({\n collectionSlug: collectionSlug ?? '',\n doc,\n documentId,\n locale: localFromContext?.code,\n options: {\n instructionId: currentInstructionId,\n },\n } satisfies Parameters<GenerateTextarea>[0]),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n .then(async (uploadResponse) => {\n if (uploadResponse.ok) {\n const json = await uploadResponse.json()\n const { job, result } = json || {}\n if (result) {\n // Set the upload ID\n setValue(result?.id)\n setHistory(result?.id)\n\n // Show toast to prompt user to save\n toast.success('Image generated successfully! Click Save to see the preview.')\n\n return uploadResponse\n }\n\n // Async job: poll AI Jobs collection for status/progress/result_id\n if (job && job.id) {\n setIsJobActive(true)\n const cancelled = false\n let attempts = 0\n const maxAttempts = 600 // up to ~10 minutes @ 1s\n\n // Basic in-hook state via closure variables; UI will re-render off fetches below\n const poll = async (): Promise<void> => {\n if (cancelled) {\n return\n }\n try {\n const res = await fetch(`${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`, {\n credentials: 'include',\n })\n if (res.ok) {\n const jobDoc = await res.json()\n const { progress, result_id, status } = jobDoc || {}\n setJobStatus(status)\n setJobProgress(progress ?? 0)\n // When result present, set field and finish\n if (status === 'completed' && result_id) {\n // Force upload field to refetch by clearing then setting the ID\n setValue(null)\n setTimeout(() => {\n setValue(result_id)\n }, 0)\n setHistory(result_id)\n setIsJobActive(false)\n return\n }\n if (status === 'failed') {\n setIsJobActive(false)\n throw new Error('Video generation failed')\n }\n }\n } catch (e) {\n // silent retry\n }\n\n attempts += 1\n if (!cancelled && attempts < maxAttempts) {\n setTimeout(poll, 1000)\n }\n }\n setTimeout(poll, 1000)\n return uploadResponse\n }\n\n throw new Error('generateUpload: Unexpected response')\n } else {\n const { errors = [] } = await uploadResponse.json()\n const errStr = errors.map((error: any) => error.message).join(', ')\n throw new Error(errStr)\n }\n })\n .catch((error) => {\n toast.error(`Failed to generate: ${error.message}`)\n console.error(\n 'Error generating or setting your upload, please set it manually if its saved in your media files.',\n error,\n )\n })\n }, [\n getData,\n localFromContext?.code,\n instructionIdRef,\n setValue,\n documentId,\n collectionSlug,\n serverURL,\n api,\n setHistory,\n ])\n\n return {\n generateUpload,\n isJobActive,\n jobProgress,\n jobStatus,\n }\n}\n"],"names":["toast","useConfig","useDocumentInfo","useForm","useLocale","useCallback","useState","PLUGIN_AI_JOBS_TABLE","PLUGIN_API_ENDPOINT_GENERATE_UPLOAD","useHistory","useGenerateUpload","instructionIdRef","setValue","config","routes","api","serverURL","id","documentId","collectionSlug","localFromContext","getData","set","setHistory","jobStatus","setJobStatus","undefined","jobProgress","setJobProgress","isJobActive","setIsJobActive","generateUpload","doc","currentInstructionId","current","fetch","body","JSON","stringify","locale","code","options","instructionId","credentials","headers","method","then","uploadResponse","ok","json","job","result","success","cancelled","attempts","maxAttempts","poll","res","jobDoc","progress","result_id","status","setTimeout","Error","e","errors","errStr","map","error","message","join","catch","console"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/ui/Compose/hooks/useGenerateUpload.ts"],"sourcesContent":["import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui'\nimport { type RefObject, useCallback, useState } from 'react'\n\nimport type { GenerateTextarea } from '../../../types.js'\n\nimport { PLUGIN_AI_JOBS_TABLE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD } from '../../../defaults.js'\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\nimport { useHistory } from './useHistory.js'\n\ntype UseGenerateUploadParams = {\n instructionIdRef: RefObject<string>\n}\n\nexport const useGenerateUpload = ({ instructionIdRef }: UseGenerateUploadParams) => {\n const { config } = useConfig()\n const {\n routes: { api },\n serverURL,\n } = config\n const { id: documentId, collectionSlug } = useDocumentInfo()\n const localFromContext = useLocale()\n const { getData } = useForm()\n const { set: setHistory } = useHistory()\n\n const { field, path: pathFromContext } = useFieldProps()\n const { setValue } = useField<any>({\n path: pathFromContext ?? '',\n })\n\n // Async job UI state\n const [jobStatus, setJobStatus] = useState<string | undefined>(undefined)\n const [jobProgress, setJobProgress] = useState<number>(0)\n const [isJobActive, setIsJobActive] = useState<boolean>(false)\n\n const generateUpload = useCallback(async () => {\n const doc = getData()\n const currentInstructionId = instructionIdRef.current\n\n return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {\n body: JSON.stringify({\n collectionSlug: collectionSlug ?? '',\n doc,\n documentId,\n locale: localFromContext?.code,\n options: {\n instructionId: currentInstructionId,\n },\n } satisfies Parameters<GenerateTextarea>[0]),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n .then(async (uploadResponse) => {\n if (uploadResponse.ok) {\n const json = await uploadResponse.json()\n const { job, result } = json || {}\n if (result) {\n setValue(result?.id)\n setHistory(result?.id)\n\n // Show toast to prompt user to save\n toast.success('Image generated successfully! Click Save to see the preview.')\n\n return uploadResponse\n }\n\n // Async job: poll AI Jobs collection for status/progress/result_id\n if (job && job.id) {\n setIsJobActive(true)\n const cancelled = false\n let attempts = 0\n const maxAttempts = 600 // up to ~10 minutes @ 1s\n\n // Basic in-hook state via closure variables; UI will re-render off fetches below\n const poll = async (): Promise<void> => {\n if (cancelled) {\n return\n }\n try {\n const res = await fetch(`${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`, {\n credentials: 'include',\n })\n if (res.ok) {\n const jobDoc = await res.json()\n const { progress, result_id, status } = jobDoc || {}\n setJobStatus(status)\n setJobProgress(progress ?? 0)\n // When result present, set field and finish\n if (status === 'completed' && result_id) {\n let valueToSet = result_id\n\n // Attempt to fetch full document for immediate preview\n if (field && 'relationTo' in field && typeof field.relationTo === 'string') {\n let attempts = 0\n const maxAttempts = 3\n while (attempts < maxAttempts) {\n try {\n const docRes = await fetch(\n `${serverURL}${api}/${field.relationTo}/${result_id}`,\n {\n credentials: 'include',\n },\n )\n if (docRes.ok) {\n const doc = await docRes.json()\n // Verify we have a URL for preview\n if (doc && doc.url) {\n valueToSet = doc\n break\n }\n }\n } catch (e) {\n console.error('Failed to fetch generated document for preview:', e)\n }\n attempts++\n if (attempts < maxAttempts) {\n await new Promise((resolve) => setTimeout(resolve, 500))\n }\n }\n }\n\n setValue(valueToSet)\n setHistory(result_id)\n setIsJobActive(false)\n return\n }\n if (status === 'failed') {\n setIsJobActive(false)\n throw new Error('Video generation failed')\n }\n }\n } catch (_) {\n // silent retry\n }\n\n attempts += 1\n if (!cancelled && attempts < maxAttempts) {\n setTimeout(poll, 1000)\n }\n }\n setTimeout(poll, 1000)\n return uploadResponse\n }\n\n throw new Error('generateUpload: Unexpected response')\n } else {\n const { errors = [] } = await uploadResponse.json()\n const errStr = errors.map((error: any) => error.message).join(', ')\n throw new Error(errStr)\n }\n })\n .catch((error) => {\n toast.error(`Failed to generate: ${error.message}`)\n console.error(\n 'Error generating or setting your upload, please set it manually if its saved in your media files.',\n error,\n )\n })\n }, [\n getData,\n localFromContext?.code,\n instructionIdRef,\n // setValue,\n documentId,\n collectionSlug,\n serverURL,\n api,\n setHistory,\n ])\n\n return {\n generateUpload,\n isJobActive,\n jobProgress,\n jobStatus,\n }\n}\n"],"names":["toast","useConfig","useDocumentInfo","useField","useForm","useLocale","useCallback","useState","PLUGIN_AI_JOBS_TABLE","PLUGIN_API_ENDPOINT_GENERATE_UPLOAD","useFieldProps","useHistory","useGenerateUpload","instructionIdRef","config","routes","api","serverURL","id","documentId","collectionSlug","localFromContext","getData","set","setHistory","field","path","pathFromContext","setValue","jobStatus","setJobStatus","undefined","jobProgress","setJobProgress","isJobActive","setIsJobActive","generateUpload","doc","currentInstructionId","current","fetch","body","JSON","stringify","locale","code","options","instructionId","credentials","headers","method","then","uploadResponse","ok","json","job","result","success","cancelled","attempts","maxAttempts","poll","res","jobDoc","progress","result_id","status","valueToSet","relationTo","docRes","url","e","console","error","Promise","resolve","setTimeout","Error","_","errors","errStr","map","message","join","catch"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/Compose/hooks/useStreamingUpdate.ts"],"sourcesContent":["import { useForm } from '@payloadcms/ui'\nimport type { LexicalEditor } from 'lexical'\nimport { useEffect, useRef } from 'react'\n\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\nimport { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js'\n\ntype UseStreamingUpdateParams = {\n editor: LexicalEditor\n isLoading: boolean\n object: any\n}\n\nexport const useStreamingUpdate = ({ editor, isLoading, object }: UseStreamingUpdateParams) => {\n const { field, path: pathFromContext } = useFieldProps()\n const { dispatchFields } = useForm()\n\n // Ref for latest object to avoid effect re-runs during high-frequency streaming\n const objectRef = useRef(object)\n objectRef.current = object\n\n useEffect(() => {\n // Only run the animation loop while loading (streaming)\n if (!isLoading) {\n return\n }\n\n let reqId: number\n\n const loop = () => {\n const currentObject = objectRef.current\n\n if (currentObject) {\n if (field?.type === 'richText') {\n setSafeLexicalState(currentObject, editor)\n } else if (field && 'name' in field && currentObject[field.name]) {\n // Use dispatchFields for high-frequency streaming updates to avoid re-renders\n dispatchFields({\n type: 'UPDATE',\n path: pathFromContext ?? '',\n value: currentObject[field.name],\n } as any)\n }\n }\n\n // Continue loop\n reqId = requestAnimationFrame(loop)\n }\n\n // Start loop\n loop()\n\n return () => {\n cancelAnimationFrame(reqId)\n }\n }, [isLoading, editor, field, dispatchFields, pathFromContext])\n}\n"],"names":["useForm","useEffect","useRef","useFieldProps","setSafeLexicalState","useStreamingUpdate","editor","isLoading","object","field","path","pathFromContext","dispatchFields","objectRef","current","reqId","loop","currentObject","type","name","value","requestAnimationFrame","cancelAnimationFrame"],"mappings":"AAAA,SAASA,OAAO,QAAQ,iBAAgB;AAExC,SAASC,SAAS,EAAEC,MAAM,QAAQ,QAAO;AAEzC,SAASC,aAAa,QAAQ,oDAAmD;AACjF,SAASC,mBAAmB,QAAQ,4CAA2C;AAQ/E,OAAO,MAAMC,qBAAqB,CAAC,EAAEC,MAAM,EAAEC,SAAS,EAAEC,MAAM,EAA4B;IACxF,MAAM,EAAEC,KAAK,EAAEC,MAAMC,eAAe,EAAE,GAAGR;IACzC,MAAM,EAAES,cAAc,EAAE,GAAGZ;IAE3B,gFAAgF;IAChF,MAAMa,YAAYX,OAAOM;IACzBK,UAAUC,OAAO,GAAGN;IAEpBP,UAAU;QACR,wDAAwD;QACxD,IAAI,CAACM,WAAW;YACd;QACF;QAEA,IAAIQ;QAEJ,MAAMC,OAAO;YACX,MAAMC,gBAAgBJ,UAAUC,OAAO;YAEvC,IAAIG,eAAe;gBACjB,IAAIR,OAAOS,SAAS,YAAY;oBAC9Bd,oBAAoBa,eAAeX;gBACrC,OAAO,IAAIG,SAAS,UAAUA,SAASQ,aAAa,CAACR,MAAMU,IAAI,CAAC,EAAE;oBAChE,8EAA8E;oBAC9EP,eAAe;wBACbM,MAAM;wBACNR,MAAMC,mBAAmB;wBACzBS,OAAOH,aAAa,CAACR,MAAMU,IAAI,CAAC;oBAClC;gBACF;YACF;YAEA,gBAAgB;YAChBJ,QAAQM,sBAAsBL;QAChC;QAEA,aAAa;QACbA;QAEA,OAAO;YACLM,qBAAqBP;QACvB;IACF,GAAG;QAACR;QAAWD;QAAQG;QAAOG;QAAgBD;KAAgB;AAChE,EAAC"}
1
+ {"version":3,"sources":["../../../../src/ui/Compose/hooks/useStreamingUpdate.ts"],"sourcesContent":["import type { LexicalEditor } from 'lexical'\n\nimport { useForm } from '@payloadcms/ui'\nimport { useEffect, useRef } from 'react'\n\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\nimport { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js'\n\ntype UseStreamingUpdateParams = {\n editor: LexicalEditor\n isLoading: boolean\n object: any\n}\n\nexport const useStreamingUpdate = ({ editor, isLoading, object }: UseStreamingUpdateParams) => {\n const { field, path: pathFromContext } = useFieldProps()\n const { dispatchFields } = useForm()\n\n // Ref for latest object to avoid effect re-runs during high-frequency streaming\n const objectRef = useRef(object)\n objectRef.current = object\n\n useEffect(() => {\n // Only run the animation loop while loading (streaming)\n if (!isLoading) {\n return\n }\n\n let reqId: number\n\n const loop = () => {\n const currentObject = objectRef.current\n\n if (currentObject) {\n if (field?.type === 'richText') {\n setSafeLexicalState(currentObject, editor)\n } else if (field && 'name' in field && currentObject[field.name]) {\n // Use dispatchFields for high-frequency streaming updates to avoid re-renders\n dispatchFields({\n type: 'UPDATE',\n path: pathFromContext ?? '',\n value: currentObject[field.name],\n } as any)\n }\n }\n\n // Continue loop\n reqId = requestAnimationFrame(loop)\n }\n\n // Start loop\n loop()\n\n return () => {\n cancelAnimationFrame(reqId)\n }\n }, [isLoading, editor, field, dispatchFields, pathFromContext])\n}\n"],"names":["useForm","useEffect","useRef","useFieldProps","setSafeLexicalState","useStreamingUpdate","editor","isLoading","object","field","path","pathFromContext","dispatchFields","objectRef","current","reqId","loop","currentObject","type","name","value","requestAnimationFrame","cancelAnimationFrame"],"mappings":"AAEA,SAASA,OAAO,QAAQ,iBAAgB;AACxC,SAASC,SAAS,EAAEC,MAAM,QAAQ,QAAO;AAEzC,SAASC,aAAa,QAAQ,oDAAmD;AACjF,SAASC,mBAAmB,QAAQ,4CAA2C;AAQ/E,OAAO,MAAMC,qBAAqB,CAAC,EAAEC,MAAM,EAAEC,SAAS,EAAEC,MAAM,EAA4B;IACxF,MAAM,EAAEC,KAAK,EAAEC,MAAMC,eAAe,EAAE,GAAGR;IACzC,MAAM,EAAES,cAAc,EAAE,GAAGZ;IAE3B,gFAAgF;IAChF,MAAMa,YAAYX,OAAOM;IACzBK,UAAUC,OAAO,GAAGN;IAEpBP,UAAU;QACR,wDAAwD;QACxD,IAAI,CAACM,WAAW;YACd;QACF;QAEA,IAAIQ;QAEJ,MAAMC,OAAO;YACX,MAAMC,gBAAgBJ,UAAUC,OAAO;YAEvC,IAAIG,eAAe;gBACjB,IAAIR,OAAOS,SAAS,YAAY;oBAC9Bd,oBAAoBa,eAAeX;gBACrC,OAAO,IAAIG,SAAS,UAAUA,SAASQ,aAAa,CAACR,MAAMU,IAAI,CAAC,EAAE;oBAChE,8EAA8E;oBAC9EP,eAAe;wBACbM,MAAM;wBACNR,MAAMC,mBAAmB;wBACzBS,OAAOH,aAAa,CAACR,MAAMU,IAAI,CAAC;oBAClC;gBACF;YACF;YAEA,gBAAgB;YAChBJ,QAAQM,sBAAsBL;QAChC;QAEA,aAAa;QACbA;QAEA,OAAO;YACLM,qBAAqBP;QACvB;IACF,GAAG;QAACR;QAAWD;QAAQG;QAAOG;QAAgBD;KAAgB;AAChE,EAAC"}
@@ -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, useFormFields } from '@payloadcms/ui';
3
+ import { Button, toast, useField } from '@payloadcms/ui';
4
4
  import React, { useCallback, useState } from 'react';
5
5
  import { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js';
6
6
  /**
@@ -9,87 +9,58 @@ import { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js';
9
9
  * SECURE: API key is never exposed to the client
10
10
  */ export const VoicesFetcher = ({ path })=>{
11
11
  const [loading, setLoading] = useState(false);
12
- const { dispatchFields } = useForm();
13
12
  // Get the parent path (the block path)
14
13
  const fieldPath = path || '';
15
14
  const blockPath = fieldPath.split('.').slice(0, -1).join('.');
16
15
  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;
16
+ const { setValue } = useField({
17
+ path: voicesPath
18
+ });
20
19
  const fetchVoices = useCallback(async ()=>{
21
20
  setLoading(true);
21
+ const controller = new AbortController();
22
+ const timeoutId = setTimeout(()=>controller.abort(), 30000) // 30s timeout
23
+ ;
22
24
  try {
23
25
  // Call server endpoint - it will read the API key from the database
24
26
  const response = await fetch(`/api${PLUGIN_API_ENDPOINT_FETCH_VOICES}`, {
25
27
  headers: {
26
28
  'Content-Type': 'application/json'
27
29
  },
28
- method: 'POST'
30
+ method: 'POST',
31
+ signal: controller.signal
29
32
  });
33
+ clearTimeout(timeoutId);
30
34
  if (!response.ok) {
31
- const error = await response.json();
32
- throw new Error(error.message || 'Failed to fetch voices');
35
+ let errorMessage = 'Failed to fetch voices';
36
+ try {
37
+ const error = await response.json();
38
+ errorMessage = error.message || errorMessage;
39
+ } catch (e) {
40
+ // If response is not JSON (e.g. 504 Gateway Timeout HTML), use status text
41
+ errorMessage = `Error ${response.status}: ${response.statusText}`;
42
+ }
43
+ throw new Error(errorMessage);
33
44
  }
34
45
  const data = await response.json();
35
46
  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!`);
47
+ // Replace the entire array value at once
48
+ // This is much more performant than dispatching ADD_ROW actions in a loop
49
+ setValue(voices);
50
+ toast.success(`Successfully fetched ${voices.length} voices!`);
84
51
  } catch (error) {
85
- alert(`Error: ${error instanceof Error ? error.message : 'Failed to fetch voices'}`);
52
+ const msg = error instanceof Error ? error.message : 'Failed to fetch voices';
53
+ if (error instanceof Error && error.name === 'AbortError') {
54
+ toast.error('Request timed out. Please try again.');
55
+ } else {
56
+ toast.error(`Error: ${msg}`);
57
+ }
86
58
  } finally{
87
59
  setLoading(false);
60
+ clearTimeout(timeoutId);
88
61
  }
89
62
  }, [
90
- currentRowCount,
91
- dispatchFields,
92
- voicesPath
63
+ setValue
93
64
  ]);
94
65
  return /*#__PURE__*/ _jsxs("div", {
95
66
  style: {
@@ -99,9 +70,10 @@ import { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js';
99
70
  /*#__PURE__*/ _jsx(Button, {
100
71
  buttonStyle: "secondary",
101
72
  disabled: loading,
73
+ margin: false,
102
74
  onClick: fetchVoices,
103
75
  size: "medium",
104
- children: loading ? 'Fetching Voices...' : 'Fetch Voices from ElevenLabs'
76
+ children: loading ? 'Fetching Voices...' : 'Fetch Voices'
105
77
  }),
106
78
  /*#__PURE__*/ _jsx("p", {
107
79
  style: {
@@ -1 +1 @@
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
+ {"version":3,"sources":["../../../src/ui/VoicesFetcher/index.tsx"],"sourcesContent":["'use client'\n\nimport type { FieldClientComponent } from 'payload'\n\nimport { Button, toast, useField, 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\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 { setValue } = useField<Voice[]>({ path: voicesPath })\n\n const fetchVoices = useCallback(async () => {\n setLoading(true)\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), 30000) // 30s timeout\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 signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n if (!response.ok) {\n let errorMessage = 'Failed to fetch voices'\n try {\n const error = await response.json()\n errorMessage = error.message || errorMessage\n } catch (e) {\n // If response is not JSON (e.g. 504 Gateway Timeout HTML), use status text\n errorMessage = `Error ${response.status}: ${response.statusText}`\n }\n throw new Error(errorMessage)\n }\n\n const data = await response.json()\n const voices: Voice[] = data.voices || []\n\n // Replace the entire array value at once\n // This is much more performant than dispatching ADD_ROW actions in a loop\n setValue(voices)\n\n toast.success(`Successfully fetched ${voices.length} voices!`)\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : 'Failed to fetch voices'\n if (error instanceof Error && error.name === 'AbortError') {\n toast.error('Request timed out. Please try again.')\n } else {\n toast.error(`Error: ${msg}`)\n }\n } finally {\n setLoading(false)\n clearTimeout(timeoutId)\n }\n }, [setValue])\n\n return (\n <div style={{ marginBottom: '20px' }}>\n <Button buttonStyle=\"secondary\" disabled={loading} margin={false} onClick={fetchVoices} size=\"medium\">\n {loading ? 'Fetching Voices...' : 'Fetch Voices'}\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","toast","useField","React","useCallback","useState","PLUGIN_API_ENDPOINT_FETCH_VOICES","VoicesFetcher","path","loading","setLoading","fieldPath","blockPath","split","slice","join","voicesPath","setValue","fetchVoices","controller","AbortController","timeoutId","setTimeout","abort","response","fetch","headers","method","signal","clearTimeout","ok","errorMessage","error","json","message","e","status","statusText","Error","data","voices","success","length","msg","name","div","style","marginBottom","buttonStyle","disabled","margin","onClick","size","p","color","fontSize","marginTop"],"mappings":"AAAA;;AAIA,SAASA,MAAM,EAAEC,KAAK,EAAEC,QAAQ,QAAuB,iBAAgB;AACvE,OAAOC,SAASC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAEpD,SAASC,gCAAgC,QAAQ,oBAAmB;AAWpE;;;;CAIC,GACD,OAAO,MAAMC,gBAAsC,CAAC,EAAEC,IAAI,EAAE;IAC1D,MAAM,CAACC,SAASC,WAAW,GAAGL,SAAS;IAEvC,uCAAuC;IACvC,MAAMM,YAAY,AAACH,QAAmB;IACtC,MAAMI,YAAYD,UAAUE,KAAK,CAAC,KAAKC,KAAK,CAAC,GAAG,CAAC,GAAGC,IAAI,CAAC;IACzD,MAAMC,aAAa,CAAC,EAAEJ,UAAU,OAAO,CAAC;IAExC,MAAM,EAAEK,QAAQ,EAAE,GAAGf,SAAkB;QAAEM,MAAMQ;IAAW;IAE1D,MAAME,cAAcd,YAAY;QAC9BM,WAAW;QAEX,MAAMS,aAAa,IAAIC;QACvB,MAAMC,YAAYC,WAAW,IAAMH,WAAWI,KAAK,IAAI,OAAO,cAAc;;QAE5E,IAAI;YACF,oEAAoE;YACpE,MAAMC,WAAW,MAAMC,MAAM,CAAC,IAAI,EAAEnB,iCAAiC,CAAC,EAAE;gBACtEoB,SAAS;oBACP,gBAAgB;gBAClB;gBACAC,QAAQ;gBACRC,QAAQT,WAAWS,MAAM;YAC3B;YAEAC,aAAaR;YAEb,IAAI,CAACG,SAASM,EAAE,EAAE;gBAChB,IAAIC,eAAe;gBACnB,IAAI;oBACF,MAAMC,QAAQ,MAAMR,SAASS,IAAI;oBACjCF,eAAeC,MAAME,OAAO,IAAIH;gBAClC,EAAE,OAAOI,GAAG;oBACV,2EAA2E;oBAC3EJ,eAAe,CAAC,MAAM,EAAEP,SAASY,MAAM,CAAC,EAAE,EAAEZ,SAASa,UAAU,CAAC,CAAC;gBACnE;gBACA,MAAM,IAAIC,MAAMP;YAClB;YAEA,MAAMQ,OAAO,MAAMf,SAASS,IAAI;YAChC,MAAMO,SAAkBD,KAAKC,MAAM,IAAI,EAAE;YAEzC,yCAAyC;YACzC,0EAA0E;YAC1EvB,SAASuB;YAETvC,MAAMwC,OAAO,CAAC,CAAC,qBAAqB,EAAED,OAAOE,MAAM,CAAC,QAAQ,CAAC;QAC/D,EAAE,OAAOV,OAAgB;YACvB,MAAMW,MAAMX,iBAAiBM,QAAQN,MAAME,OAAO,GAAG;YACrD,IAAIF,iBAAiBM,SAASN,MAAMY,IAAI,KAAK,cAAc;gBACzD3C,MAAM+B,KAAK,CAAC;YACd,OAAO;gBACL/B,MAAM+B,KAAK,CAAC,CAAC,OAAO,EAAEW,IAAI,CAAC;YAC7B;QACF,SAAU;YACRjC,WAAW;YACXmB,aAAaR;QACf;IACF,GAAG;QAACJ;KAAS;IAEb,qBACE,MAAC4B;QAAIC,OAAO;YAAEC,cAAc;QAAO;;0BACjC,KAAC/C;gBAAOgD,aAAY;gBAAYC,UAAUxC;gBAASyC,QAAQ;gBAAOC,SAASjC;gBAAakC,MAAK;0BAC1F3C,UAAU,uBAAuB;;0BAEpC,KAAC4C;gBAAEP,OAAO;oBAAEQ,OAAO;oBAA8BC,UAAU;oBAAQC,WAAW;gBAAM;0BAAG;;;;AAM7F,EAAC"}
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { Button, useForm, useFormFields } from '@payloadcms/ui';
2
+ import { Button, toast, useField } from '@payloadcms/ui';
3
3
  import React, { useCallback, useState } from 'react';
4
4
  import { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js';
5
5
  /**
@@ -9,18 +9,15 @@ import { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js';
9
9
  */
10
10
  export const VoicesFetcher = ({ path }) => {
11
11
  const [loading, setLoading] = useState(false);
12
- const { dispatchFields } = useForm();
13
12
  // Get the parent path (the block path)
14
13
  const fieldPath = path || '';
15
14
  const blockPath = fieldPath.split('.').slice(0, -1).join('.');
16
15
  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;
16
+ const { setValue } = useField({ path: voicesPath });
22
17
  const fetchVoices = useCallback(async () => {
23
18
  setLoading(true);
19
+ const controller = new AbortController();
20
+ const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
24
21
  try {
25
22
  // Call server endpoint - it will read the API key from the database
26
23
  const response = await fetch(`/api${PLUGIN_API_ENDPOINT_FETCH_VOICES}`, {
@@ -28,48 +25,45 @@ export const VoicesFetcher = ({ path }) => {
28
25
  'Content-Type': 'application/json',
29
26
  },
30
27
  method: 'POST',
28
+ signal: controller.signal,
31
29
  });
30
+ clearTimeout(timeoutId);
32
31
  if (!response.ok) {
33
- const error = await response.json();
34
- throw new Error(error.message || 'Failed to fetch voices');
32
+ let errorMessage = 'Failed to fetch voices';
33
+ try {
34
+ const error = await response.json();
35
+ errorMessage = error.message || errorMessage;
36
+ }
37
+ catch (e) {
38
+ // If response is not JSON (e.g. 504 Gateway Timeout HTML), use status text
39
+ errorMessage = `Error ${response.status}: ${response.statusText}`;
40
+ }
41
+ throw new Error(errorMessage);
35
42
  }
36
43
  const data = await response.json();
37
44
  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!`);
45
+ // Replace the entire array value at once
46
+ // This is much more performant than dispatching ADD_ROW actions in a loop
47
+ setValue(voices);
48
+ toast.success(`Successfully fetched ${voices.length} voices!`);
62
49
  }
63
50
  catch (error) {
64
- alert(`Error: ${error instanceof Error ? error.message : 'Failed to fetch voices'}`);
51
+ const msg = error instanceof Error ? error.message : 'Failed to fetch voices';
52
+ if (error instanceof Error && error.name === 'AbortError') {
53
+ toast.error('Request timed out. Please try again.');
54
+ }
55
+ else {
56
+ toast.error(`Error: ${msg}`);
57
+ }
65
58
  }
66
59
  finally {
67
60
  setLoading(false);
61
+ clearTimeout(timeoutId);
68
62
  }
69
- }, [currentRowCount, dispatchFields, voicesPath]);
63
+ }, [setValue]);
70
64
  return (<div style={{ marginBottom: '20px' }}>
71
- <Button buttonStyle="secondary" disabled={loading} onClick={fetchVoices} size="medium">
72
- {loading ? 'Fetching Voices...' : 'Fetch Voices from ElevenLabs'}
65
+ <Button buttonStyle="secondary" disabled={loading} margin={false} onClick={fetchVoices} size="medium">
66
+ {loading ? 'Fetching Voices...' : 'Fetch Voices'}
73
67
  </Button>
74
68
  <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px', marginTop: '8px' }}>
75
69
  This will fetch all available voices from your ElevenLabs account. Make sure you have saved
@@ -92,7 +92,7 @@ import { getFieldBySchemaPath } from './getFieldBySchemaPath.js';
92
92
  /**
93
93
  * Build a smart contextual prompt based on field metadata.
94
94
  * This is used as a fallback when the user hasn't set a custom prompt.
95
- *
95
+ *
96
96
  * @param context - The context containing schema path and document data
97
97
  * @returns A contextual prompt string that can be used for AI generation
98
98
  */ export const buildSmartPrompt = (context)=>{
@@ -105,18 +105,16 @@ import { getFieldBySchemaPath } from './getFieldBySchemaPath.js';
105
105
  const parts = [];
106
106
  // Use description as primary guidance if available
107
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));
108
+ parts.push(`Field description for user: ${description}\n`);
112
109
  }
110
+ parts.push(getTypeGuidance(type, label || name));
113
111
  // Add parent context if nested
114
112
  const parentPhrase = getParentContextPhrase(parentContext);
115
113
  if (parentPhrase) {
116
114
  parts.push(parentPhrase);
117
115
  }
118
116
  // Add document title context if available
119
- const title = documentData?.title;
117
+ const title = documentData?.title || documentData?.name;
120
118
  if (title && typeof title === 'string') {
121
119
  parts.push(`in the context of "${title}"`);
122
120
  }
@@ -1 +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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"}
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) {\n return null\n }\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(`Field description for user: ${description}\\n`)\n }\n\n parts.push(getTypeGuidance(type, label || name))\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 || documentData?.name\n\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,7 +17,8 @@ export function encrypt(text, secret) {
17
17
  encrypted,
18
18
  cipher.final()
19
19
  ]);
20
- return iv.toString('hex') + ':' + encrypted.toString('hex');
20
+ const result = iv.toString('hex') + ':' + encrypted.toString('hex');
21
+ return result;
21
22
  }
22
23
  export function decrypt(text, secret) {
23
24
  if (!text) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utilities/encryption.ts"],"sourcesContent":["import crypto from 'crypto'\n\nconst algorithm = 'aes-256-cbc'\nconst ivLength = 16\n\nexport function encrypt(text: string, secret: string): string {\n if (!text) {return text}\n if (!secret) {throw new Error('No secret provided for encryption')}\n\n // Ensure secret is 32 bytes\n const key = crypto.createHash('sha256').update(secret).digest()\n const iv = crypto.randomBytes(ivLength)\n const cipher = crypto.createCipheriv(algorithm, key, iv)\n let encrypted = cipher.update(text)\n encrypted = Buffer.concat([encrypted, cipher.final()])\n return iv.toString('hex') + ':' + encrypted.toString('hex')\n}\n\nexport function decrypt(text: string, secret: string): string {\n if (!text) {return text}\n if (!secret) {throw new Error('No secret provided for decryption')}\n\n try {\n const textParts = text.split(':')\n const iv = Buffer.from(textParts.shift()!, 'hex')\n const encryptedText = Buffer.from(textParts.join(':'), 'hex')\n const key = crypto.createHash('sha256').update(secret).digest()\n const decipher = crypto.createDecipheriv(algorithm, key, iv)\n let decrypted = decipher.update(encryptedText)\n decrypted = Buffer.concat([decrypted, decipher.final()])\n return decrypted.toString()\n } catch (e) {\n // If decryption fails, return original text (might be already plain or invalid)\n return text\n }\n}\n"],"names":["crypto","algorithm","ivLength","encrypt","text","secret","Error","key","createHash","update","digest","iv","randomBytes","cipher","createCipheriv","encrypted","Buffer","concat","final","toString","decrypt","textParts","split","from","shift","encryptedText","join","decipher","createDecipheriv","decrypted","e"],"mappings":"AAAA,OAAOA,YAAY,SAAQ;AAE3B,MAAMC,YAAY;AAClB,MAAMC,WAAW;AAEjB,OAAO,SAASC,QAAQC,IAAY,EAAEC,MAAc;IAClD,IAAI,CAACD,MAAM;QAAC,OAAOA;IAAI;IACvB,IAAI,CAACC,QAAQ;QAAC,MAAM,IAAIC,MAAM;IAAoC;IAElE,4BAA4B;IAC5B,MAAMC,MAAMP,OAAOQ,UAAU,CAAC,UAAUC,MAAM,CAACJ,QAAQK,MAAM;IAC7D,MAAMC,KAAKX,OAAOY,WAAW,CAACV;IAC9B,MAAMW,SAASb,OAAOc,cAAc,CAACb,WAAWM,KAAKI;IACrD,IAAII,YAAYF,OAAOJ,MAAM,CAACL;IAC9BW,YAAYC,OAAOC,MAAM,CAAC;QAACF;QAAWF,OAAOK,KAAK;KAAG;IACrD,OAAOP,GAAGQ,QAAQ,CAAC,SAAS,MAAMJ,UAAUI,QAAQ,CAAC;AACvD;AAEA,OAAO,SAASC,QAAQhB,IAAY,EAAEC,MAAc;IAClD,IAAI,CAACD,MAAM;QAAC,OAAOA;IAAI;IACvB,IAAI,CAACC,QAAQ;QAAC,MAAM,IAAIC,MAAM;IAAoC;IAElE,IAAI;QACF,MAAMe,YAAYjB,KAAKkB,KAAK,CAAC;QAC7B,MAAMX,KAAKK,OAAOO,IAAI,CAACF,UAAUG,KAAK,IAAK;QAC3C,MAAMC,gBAAgBT,OAAOO,IAAI,CAACF,UAAUK,IAAI,CAAC,MAAM;QACvD,MAAMnB,MAAMP,OAAOQ,UAAU,CAAC,UAAUC,MAAM,CAACJ,QAAQK,MAAM;QAC7D,MAAMiB,WAAW3B,OAAO4B,gBAAgB,CAAC3B,WAAWM,KAAKI;QACzD,IAAIkB,YAAYF,SAASlB,MAAM,CAACgB;QAChCI,YAAYb,OAAOC,MAAM,CAAC;YAACY;YAAWF,SAAST,KAAK;SAAG;QACvD,OAAOW,UAAUV,QAAQ;IAC3B,EAAE,OAAOW,GAAG;QACV,gFAAgF;QAChF,OAAO1B;IACT;AACF"}
1
+ {"version":3,"sources":["../../src/utilities/encryption.ts"],"sourcesContent":["import crypto from 'crypto'\n\nconst algorithm = 'aes-256-cbc'\nconst ivLength = 16\n\nexport function encrypt(text: string, secret: string): string {\n if (!text) {\n return text\n }\n if (!secret) {\n throw new Error('No secret provided for encryption')\n }\n\n // Ensure secret is 32 bytes\n const key = crypto.createHash('sha256').update(secret).digest()\n const iv = crypto.randomBytes(ivLength)\n const cipher = crypto.createCipheriv(algorithm, key, iv)\n let encrypted = cipher.update(text)\n encrypted = Buffer.concat([encrypted, cipher.final()])\n const result = iv.toString('hex') + ':' + encrypted.toString('hex')\n\n return result\n}\n\nexport function decrypt(text: string, secret: string): string {\n if (!text) {\n return text\n }\n if (!secret) {\n throw new Error('No secret provided for decryption')\n }\n\n try {\n const textParts = text.split(':')\n const iv = Buffer.from(textParts.shift()!, 'hex')\n const encryptedText = Buffer.from(textParts.join(':'), 'hex')\n const key = crypto.createHash('sha256').update(secret).digest()\n const decipher = crypto.createDecipheriv(algorithm, key, iv)\n let decrypted = decipher.update(encryptedText)\n decrypted = Buffer.concat([decrypted, decipher.final()])\n return decrypted.toString()\n } catch (e) {\n // If decryption fails, return original text (might be already plain or invalid)\n return text\n }\n}\n"],"names":["crypto","algorithm","ivLength","encrypt","text","secret","Error","key","createHash","update","digest","iv","randomBytes","cipher","createCipheriv","encrypted","Buffer","concat","final","result","toString","decrypt","textParts","split","from","shift","encryptedText","join","decipher","createDecipheriv","decrypted","e"],"mappings":"AAAA,OAAOA,YAAY,SAAQ;AAE3B,MAAMC,YAAY;AAClB,MAAMC,WAAW;AAEjB,OAAO,SAASC,QAAQC,IAAY,EAAEC,MAAc;IAClD,IAAI,CAACD,MAAM;QACT,OAAOA;IACT;IACA,IAAI,CAACC,QAAQ;QACX,MAAM,IAAIC,MAAM;IAClB;IAEA,4BAA4B;IAC5B,MAAMC,MAAMP,OAAOQ,UAAU,CAAC,UAAUC,MAAM,CAACJ,QAAQK,MAAM;IAC7D,MAAMC,KAAKX,OAAOY,WAAW,CAACV;IAC9B,MAAMW,SAASb,OAAOc,cAAc,CAACb,WAAWM,KAAKI;IACrD,IAAII,YAAYF,OAAOJ,MAAM,CAACL;IAC9BW,YAAYC,OAAOC,MAAM,CAAC;QAACF;QAAWF,OAAOK,KAAK;KAAG;IACrD,MAAMC,SAASR,GAAGS,QAAQ,CAAC,SAAS,MAAML,UAAUK,QAAQ,CAAC;IAE7D,OAAOD;AACT;AAEA,OAAO,SAASE,QAAQjB,IAAY,EAAEC,MAAc;IAClD,IAAI,CAACD,MAAM;QACT,OAAOA;IACT;IACA,IAAI,CAACC,QAAQ;QACX,MAAM,IAAIC,MAAM;IAClB;IAEA,IAAI;QACF,MAAMgB,YAAYlB,KAAKmB,KAAK,CAAC;QAC7B,MAAMZ,KAAKK,OAAOQ,IAAI,CAACF,UAAUG,KAAK,IAAK;QAC3C,MAAMC,gBAAgBV,OAAOQ,IAAI,CAACF,UAAUK,IAAI,CAAC,MAAM;QACvD,MAAMpB,MAAMP,OAAOQ,UAAU,CAAC,UAAUC,MAAM,CAACJ,QAAQK,MAAM;QAC7D,MAAMkB,WAAW5B,OAAO6B,gBAAgB,CAAC5B,WAAWM,KAAKI;QACzD,IAAImB,YAAYF,SAASnB,MAAM,CAACiB;QAChCI,YAAYd,OAAOC,MAAM,CAAC;YAACa;YAAWF,SAASV,KAAK;SAAG;QACvD,OAAOY,UAAUV,QAAQ;IAC3B,EAAE,OAAOW,GAAG;QACV,gFAAgF;QAChF,OAAO3B;IACT;AACF"}
@@ -1,4 +1,3 @@
1
- import { defaultSeedPrompts } from '../ai/prompts.js';
2
1
  import { PLUGIN_INSTRUCTIONS_TABLE } from '../defaults.js';
3
2
  import { updateFieldsConfig } from './updateFieldsConfig.js';
4
3
  export const seedProperties = async ({ enabledCollections, req })=>{
@@ -17,7 +16,7 @@ export const seedProperties = async ({ enabledCollections, req })=>{
17
16
  // Use the side-effect of getting schemaPathMap from it
18
17
  const { schemaPathMap } = updateFieldsConfig(collectionConfig);
19
18
  for (const [schemaPath, fieldInfo] of Object.entries(schemaPathMap)){
20
- const { type, custom, label, relationTo } = fieldInfo;
19
+ const { type, custom, relationTo } = fieldInfo;
21
20
  // Check if instruction already exists
22
21
  const existingInstruction = await payload.find({
23
22
  collection: PLUGIN_INSTRUCTIONS_TABLE,
@@ -62,25 +61,9 @@ export const seedProperties = async ({ enabledCollections, req })=>{
62
61
  }
63
62
  continue;
64
63
  }
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
- }
64
+ // Use custom prompts if provided, otherwise leave empty
65
+ const prompt = custom?.ai?.prompt || '';
66
+ const system = custom?.ai?.system || '';
84
67
  // Determine model-id based on field type
85
68
  let modelId = 'text';
86
69
  if (type === 'richText') {
@@ -97,13 +80,13 @@ export const seedProperties = async ({ enabledCollections, req })=>{
97
80
  await payload.create({
98
81
  collection: PLUGIN_INSTRUCTIONS_TABLE,
99
82
  data: {
100
- prompt,
101
- system,
102
83
  disabled: false,
103
84
  'field-type': type,
104
85
  'model-id': modelId,
86
+ prompt,
105
87
  'relation-to': relationTo,
106
- 'schema-path': schemaPath
88
+ 'schema-path': schemaPath,
89
+ system
107
90
  },
108
91
  overrideAccess: true
109
92
  });