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

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 (187) 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 +13 -28
  4. package/dist/ai/core/media/image/handlers/multimodal.js.map +1 -1
  5. package/dist/ai/core/media/image/handlers/standard.js +10 -7
  6. package/dist/ai/core/media/image/handlers/standard.js.map +1 -1
  7. package/dist/ai/core/media/speech/generateSpeech.js +8 -8
  8. package/dist/ai/core/media/speech/generateSpeech.js.map +1 -1
  9. package/dist/ai/core/media/types.d.ts +1 -1
  10. package/dist/ai/core/media/types.js.map +1 -1
  11. package/dist/ai/core/streamObject.js +3 -3
  12. package/dist/ai/core/streamObject.js.map +1 -1
  13. package/dist/ai/core/types.d.ts +3 -0
  14. package/dist/ai/core/types.js.map +1 -1
  15. package/dist/ai/prompts.d.ts +1 -2
  16. package/dist/ai/prompts.js +0 -110
  17. package/dist/ai/prompts.js.map +1 -1
  18. package/dist/ai/providers/blocks/anthropic.js +2 -1
  19. package/dist/ai/providers/blocks/anthropic.js.map +1 -1
  20. package/dist/ai/providers/blocks/elevenlabs.js +3 -2
  21. package/dist/ai/providers/blocks/elevenlabs.js.map +1 -1
  22. package/dist/ai/providers/blocks/fal.js +2 -1
  23. package/dist/ai/providers/blocks/fal.js.map +1 -1
  24. package/dist/ai/providers/blocks/google.js +12 -6
  25. package/dist/ai/providers/blocks/google.js.map +1 -1
  26. package/dist/ai/providers/blocks/openai-compatible.js +2 -1
  27. package/dist/ai/providers/blocks/openai-compatible.js.map +1 -1
  28. package/dist/ai/providers/blocks/openai.js +3 -2
  29. package/dist/ai/providers/blocks/openai.js.map +1 -1
  30. package/dist/ai/providers/blocks/xai.js +2 -1
  31. package/dist/ai/providers/blocks/xai.js.map +1 -1
  32. package/dist/ai/providers/icons.d.ts +7 -0
  33. package/dist/ai/providers/icons.js +9 -0
  34. package/dist/ai/providers/icons.js.map +1 -0
  35. package/dist/ai/providers/registry.js +36 -26
  36. package/dist/ai/providers/registry.js.map +1 -1
  37. package/dist/ai/utils/filterEditorSchemaByNodes.d.ts +9 -0
  38. package/dist/ai/utils/filterEditorSchemaByNodes.js +30 -3
  39. package/dist/ai/utils/filterEditorSchemaByNodes.js.map +1 -1
  40. package/dist/ai/utils/nodeToSchemaMap.d.ts +22 -0
  41. package/dist/ai/utils/nodeToSchemaMap.js +72 -0
  42. package/dist/ai/utils/nodeToSchemaMap.js.map +1 -0
  43. package/dist/collections/AIJobs.js +1 -1
  44. package/dist/collections/AIJobs.js.map +1 -1
  45. package/dist/collections/AIProviders.d.ts +2 -0
  46. package/dist/collections/{AISettings.js → AIProviders.js} +53 -25
  47. package/dist/collections/AIProviders.js.map +1 -0
  48. package/dist/collections/Instructions.js +45 -4
  49. package/dist/collections/Instructions.js.map +1 -1
  50. package/dist/defaults.d.ts +1 -0
  51. package/dist/defaults.js +8 -0
  52. package/dist/defaults.js.map +1 -1
  53. package/dist/endpoints/chat.d.ts +4 -0
  54. package/dist/endpoints/fetchFields.js +10 -0
  55. package/dist/endpoints/fetchFields.js.map +1 -1
  56. package/dist/endpoints/fetchVoices.js +42 -25
  57. package/dist/endpoints/fetchVoices.js.map +1 -1
  58. package/dist/endpoints/index.js +248 -35
  59. package/dist/endpoints/index.js.map +1 -1
  60. package/dist/exports/client.d.ts +1 -1
  61. package/dist/exports/client.js +1 -1
  62. package/dist/exports/client.js.map +1 -1
  63. package/dist/exports/fields.d.ts +1 -0
  64. package/dist/exports/fields.js +1 -0
  65. package/dist/exports/fields.js.map +1 -1
  66. package/dist/fields/ArrayComposeField/ArrayComposeField.d.ts +15 -0
  67. package/dist/fields/ArrayComposeField/ArrayComposeField.js +87 -0
  68. package/dist/fields/ArrayComposeField/ArrayComposeField.js.map +1 -0
  69. package/dist/fields/ArrayComposeField/ArrayComposeField.jsx +73 -0
  70. package/dist/fields/PromptEditorField/PromptEditorField.js +7 -2
  71. package/dist/fields/PromptEditorField/PromptEditorField.js.map +1 -1
  72. package/dist/fields/PromptEditorField/PromptEditorField.jsx +5 -2
  73. package/dist/index.d.ts +3 -1
  74. package/dist/index.js +2 -1
  75. package/dist/index.js.map +1 -1
  76. package/dist/payload-ai.d.ts +152 -0
  77. package/dist/plugin.js +18 -34
  78. package/dist/plugin.js.map +1 -1
  79. package/dist/providers/InstructionsProvider/InstructionsProvider.js +47 -15
  80. package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
  81. package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +39 -16
  82. package/dist/providers/InstructionsProvider/context.d.ts +3 -0
  83. package/dist/providers/InstructionsProvider/context.js +2 -0
  84. package/dist/providers/InstructionsProvider/context.js.map +1 -1
  85. package/dist/providers/InstructionsProvider/useInstructions.js +22 -3
  86. package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
  87. package/dist/styles.d.ts +11 -0
  88. package/dist/types/handlebars-async-helpers.d.ts +1 -0
  89. package/dist/types/handlebars-dist-handlebars.d.ts +1 -0
  90. package/dist/types/react-mentions.d.ts +1 -0
  91. package/dist/types.d.ts +36 -7
  92. package/dist/types.js +1 -0
  93. package/dist/types.js.map +1 -1
  94. package/dist/ui/AIConfigDashboard/index.d.ts +1 -1
  95. package/dist/ui/AIConfigDashboard/index.js +201 -23
  96. package/dist/ui/AIConfigDashboard/index.js.map +1 -1
  97. package/dist/ui/AIConfigDashboard/index.jsx +166 -15
  98. package/dist/ui/Compose/Compose.d.ts +1 -0
  99. package/dist/ui/Compose/Compose.js +23 -4
  100. package/dist/ui/Compose/Compose.js.map +1 -1
  101. package/dist/ui/Compose/Compose.jsx +23 -4
  102. package/dist/ui/Compose/UndoRedoActions.d.ts +2 -2
  103. package/dist/ui/Compose/UndoRedoActions.js +8 -5
  104. package/dist/ui/Compose/UndoRedoActions.js.map +1 -1
  105. package/dist/ui/Compose/UndoRedoActions.jsx +6 -5
  106. package/dist/ui/Compose/compose.module.css +56 -16
  107. package/dist/ui/Compose/hooks/menu/TranslateMenu.d.ts +5 -0
  108. package/dist/ui/Compose/hooks/menu/TranslateMenu.js +45 -4
  109. package/dist/ui/Compose/hooks/menu/TranslateMenu.js.map +1 -1
  110. package/dist/ui/Compose/hooks/menu/TranslateMenu.jsx +41 -5
  111. package/dist/ui/Compose/hooks/menu/itemsMap.js +12 -6
  112. package/dist/ui/Compose/hooks/menu/itemsMap.js.map +1 -1
  113. package/dist/ui/Compose/hooks/menu/menu.module.scss +4 -1
  114. package/dist/ui/Compose/hooks/menu/useMenu.js +26 -15
  115. package/dist/ui/Compose/hooks/menu/useMenu.js.map +1 -1
  116. package/dist/ui/Compose/hooks/menu/useMenu.jsx +25 -12
  117. package/dist/ui/Compose/hooks/useActiveFieldTracking.js +34 -0
  118. package/dist/ui/Compose/hooks/useActiveFieldTracking.js.map +1 -1
  119. package/dist/ui/Compose/hooks/useGenerate.js +26 -174
  120. package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
  121. package/dist/ui/Compose/hooks/useGenerateUpload.d.ts +11 -0
  122. package/dist/ui/Compose/hooks/useGenerateUpload.js +156 -0
  123. package/dist/ui/Compose/hooks/useGenerateUpload.js.map +1 -0
  124. package/dist/ui/Compose/hooks/useHistory.d.ts +0 -1
  125. package/dist/ui/Compose/hooks/useHistory.js +65 -25
  126. package/dist/ui/Compose/hooks/useHistory.js.map +1 -1
  127. package/dist/ui/Compose/hooks/useStreamingUpdate.d.ts +8 -0
  128. package/dist/ui/Compose/hooks/useStreamingUpdate.js +48 -0
  129. package/dist/ui/Compose/hooks/useStreamingUpdate.js.map +1 -0
  130. package/dist/ui/ConfigDashboard/index.d.ts +2 -0
  131. package/dist/ui/ConfigDashboard/index.js +224 -0
  132. package/dist/ui/ConfigDashboard/index.js.map +1 -0
  133. package/dist/ui/ConfigDashboard/index.jsx +175 -0
  134. package/dist/ui/DynamicModelSelect/index.js +1 -1
  135. package/dist/ui/DynamicModelSelect/index.js.map +1 -1
  136. package/dist/ui/DynamicModelSelect/index.jsx +1 -1
  137. package/dist/ui/DynamicProviderSelect/index.js +1 -1
  138. package/dist/ui/DynamicProviderSelect/index.js.map +1 -1
  139. package/dist/ui/DynamicProviderSelect/index.jsx +1 -1
  140. package/dist/ui/DynamicVoiceSelect/index.js +63 -11
  141. package/dist/ui/DynamicVoiceSelect/index.js.map +1 -1
  142. package/dist/ui/DynamicVoiceSelect/index.jsx +47 -14
  143. package/dist/ui/EncryptedTextField/index.js +4 -4
  144. package/dist/ui/EncryptedTextField/index.js.map +1 -1
  145. package/dist/ui/EncryptedTextField/index.jsx +4 -4
  146. package/dist/ui/ProviderOptionsEditor/index.js +1 -1
  147. package/dist/ui/ProviderOptionsEditor/index.js.map +1 -1
  148. package/dist/ui/ProviderOptionsEditor/index.jsx +1 -1
  149. package/dist/ui/VoicesFetcher/index.js +34 -16
  150. package/dist/ui/VoicesFetcher/index.js.map +1 -1
  151. package/dist/ui/VoicesFetcher/index.jsx +32 -15
  152. package/dist/utilities/buildSmartPrompt.d.ts +22 -0
  153. package/dist/utilities/buildSmartPrompt.js +141 -0
  154. package/dist/utilities/buildSmartPrompt.js.map +1 -0
  155. package/dist/utilities/encryption.js +2 -1
  156. package/dist/utilities/encryption.js.map +1 -1
  157. package/dist/utilities/fieldToJsonSchema.js +32 -3
  158. package/dist/utilities/fieldToJsonSchema.js.map +1 -1
  159. package/dist/utilities/resolveImageReferences.d.ts +3 -1
  160. package/dist/utilities/resolveImageReferences.js +21 -2
  161. package/dist/utilities/resolveImageReferences.js.map +1 -1
  162. package/dist/utilities/seedProperties.d.ts +7 -0
  163. package/dist/utilities/seedProperties.js +100 -0
  164. package/dist/utilities/seedProperties.js.map +1 -0
  165. package/dist/utilities/setSafeLexicalState.js +79 -6
  166. package/dist/utilities/setSafeLexicalState.js.map +1 -1
  167. package/dist/utilities/updateFieldsConfig.d.ts +1 -1
  168. package/dist/utilities/updateFieldsConfig.js +9 -2
  169. package/dist/utilities/updateFieldsConfig.js.map +1 -1
  170. package/package.json +35 -33
  171. package/dist/collections/AISettings.d.ts +0 -2
  172. package/dist/collections/AISettings.js.map +0 -1
  173. package/dist/endpoints/chat.d.js +0 -3
  174. package/dist/endpoints/chat.d.js.map +0 -1
  175. package/dist/init.d.ts +0 -7
  176. package/dist/init.js +0 -135
  177. package/dist/init.js.map +0 -1
  178. package/dist/payload-ai.d.js +0 -3
  179. package/dist/payload-ai.d.js.map +0 -1
  180. package/dist/styles.d.js +0 -2
  181. package/dist/styles.d.js.map +0 -1
  182. package/dist/types/handlebars-async-helpers.d.js +0 -2
  183. package/dist/types/handlebars-async-helpers.d.js.map +0 -1
  184. package/dist/types/handlebars-dist-handlebars.d.js +0 -2
  185. package/dist/types/handlebars-dist-handlebars.d.js.map +0 -1
  186. package/dist/types/react-mentions.d.js +0 -2
  187. package/dist/types/react-mentions.d.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { useField } from '@payloadcms/ui';
2
+ import { Button, useField } from '@payloadcms/ui';
3
3
  import React, { useState } from 'react';
4
4
  export const EncryptedTextField = ({ label, path, required }) => {
5
5
  const { setValue, value } = useField({ path });
@@ -24,12 +24,12 @@ export const EncryptedTextField = ({ label, path, required }) => {
24
24
  ✓ Configured
25
25
  </span>
26
26
  </div>
27
- <button className="btn btn--style-secondary btn--size-small" onClick={() => {
27
+ <Button buttonStyle="secondary" onClick={() => {
28
28
  setValue('');
29
29
  setIsEditing(true);
30
- }} type="button">
30
+ }} size="medium">
31
31
  Change
32
- </button>
32
+ </Button>
33
33
  </div>) : (<input onChange={(e) => setValue(e.target.value)} placeholder="sk-..." style={{ width: '100%' }} type="password" value={value || ''}/>)}
34
34
  </div>);
35
35
  };
@@ -86,7 +86,7 @@ export const ProviderOptionsEditor = (props)=>{
86
86
  const [aiSettings, setAiSettings] = useState(null);
87
87
  // Fetch AI Settings to get current provider defaults
88
88
  useEffect(()=>{
89
- fetch('/api/globals/ai-settings?depth=1').then((res)=>res.json()).then((data)=>setAiSettings(data)).catch((err)=>console.error('Error fetching AI settings:', err));
89
+ fetch('/api/globals/ai-providers?depth=1').then((res)=>res.json()).then((data)=>setAiSettings(data)).catch((err)=>console.error('Error fetching AI settings:', err));
90
90
  }, []);
91
91
  // Get the configured default options from AI Settings for this provider
92
92
  const configuredDefaults = useMemo(()=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/ProviderOptionsEditor/index.tsx"],"sourcesContent":["'use client'\n\nimport { RenderFields, useField, useFormFields } from '@payloadcms/ui'\nimport React, { useEffect, useMemo, useState } from 'react'\n\nimport { allProviderBlocks } from '../../ai/providers/blocks/index.js'\n\ntype UseCase = 'image' | 'text' | 'tts' | 'video'\n\ninterface ProviderOptionsEditorProps {\n name?: string\n path: string\n}\n\n/**\n * Find a field by name within a block's fields, searching through tabs\n */\nfunction findFieldInBlock(block: any, fieldName: string): any | undefined {\n const searchFields = (fields: any[]): any | undefined => {\n for (const field of fields) {\n if ('name' in field && field.name === fieldName) {\n return field\n }\n if (field.type === 'tabs' && 'tabs' in field) {\n for (const tab of field.tabs) {\n const found = searchFields(tab.fields)\n if (found) {\n return found\n }\n }\n }\n if (field.type === 'group' && 'fields' in field) {\n const found = searchFields(field.fields)\n if (found) {\n return found\n }\n }\n }\n return undefined\n }\n\n return searchFields(block.fields)\n}\n\n/**\n * Get provider options fields for a given provider and use case\n */\nfunction getProviderOptionsFields(providerSlug: string, useCase: UseCase): any[] {\n const block = allProviderBlocks.find((b) => b.slug === providerSlug)\n if (!block) {\n return []\n }\n\n const groupName = `${useCase}ProviderOptions`\n const optionsGroup = findFieldInBlock(block, groupName)\n\n if (optionsGroup && optionsGroup.type === 'group' && 'fields' in optionsGroup) {\n return optionsGroup.fields\n }\n\n return []\n}\n\nexport const ProviderOptionsEditor: React.FC<ProviderOptionsEditorProps> = (props) => {\n const { path } = props\n\n // Get parent path to find sibling provider field\n const parentPath = path.split('.').slice(0, -1).join('.')\n const providerField = useFormFields(([fields]) => fields[`${parentPath}.provider`])\n const provider = providerField?.value as string\n\n // Infer use case from path\n // Handles:\n // - AISettings: 'defaults.text.options' -> useCase is 'text'\n // - Instructions: 'text-settings.providerOptions' -> useCase is 'text'\n const useCase: UseCase = useMemo(() => {\n // Check for AISettings paths first (e.g., 'defaults.text.options')\n const pathParts = path.split('.')\n const parentName = pathParts[pathParts.length - 2]\n\n if (['image', 'text', 'tts', 'video'].includes(parentName)) {\n return parentName as UseCase\n }\n\n // Check for Instructions paths\n if (path.includes('tts-settings')) {\n return 'tts'\n }\n if (path.includes('image-settings')) {\n return 'image'\n }\n if (path.includes('video-settings')) {\n return 'video'\n }\n return 'text'\n }, [path])\n\n const { setValue, value } = useField<Record<string, any>>({ path })\n const [aiSettings, setAiSettings] = useState<any>(null)\n\n // Fetch AI Settings to get current provider defaults\n useEffect(() => {\n fetch('/api/globals/ai-settings?depth=1')\n .then((res) => res.json())\n .then((data) => setAiSettings(data))\n .catch((err) => console.error('Error fetching AI settings:', err))\n }, [])\n\n // Get the configured default options from AI Settings for this provider\n const configuredDefaults = useMemo(() => {\n if (!provider || !aiSettings) {\n return null\n }\n\n const providerBlock = aiSettings.providers?.find(\n (p: any) => p.blockType === provider && p.enabled,\n )\n if (!providerBlock) {\n return null\n }\n\n // Get provider options by use case\n const optionsKey = `${useCase}ProviderOptions`\n return providerBlock[optionsKey] || null\n }, [provider, useCase, aiSettings])\n\n // Get field definitions from provider block\n const fields = useMemo(() => {\n if (!provider) {\n return []\n }\n return getProviderOptionsFields(provider, useCase)\n }, [provider, useCase])\n\n // Check if there are any overrides set\n const hasOverrides = useMemo(() => {\n return value && Object.keys(value).length > 0\n }, [value])\n\n if (!provider) {\n return (\n <div className=\"field-type\" style={{ padding: '12px 0' }}>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px', margin: 0 }}>\n Please select a provider first to configure options.\n </p>\n </div>\n )\n }\n\n if (fields.length === 0) {\n return (\n <div className=\"field-type\" style={{ padding: '12px 0' }}>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px', margin: 0 }}>\n No configurable options available for {provider} ({useCase}).\n </p>\n </div>\n )\n }\n\n return (\n <div className=\"field-type provider-options-editor\">\n <div style={{ marginBottom: '16px' }}>\n <label className=\"field-label\" style={{ display: 'block', marginBottom: '8px' }}>\n Provider Options\n </label>\n\n {configuredDefaults && (\n <div\n style={{\n background: 'var(--theme-elevation-50)',\n border: '1px solid var(--theme-elevation-100)',\n borderRadius: '4px',\n fontSize: '12px',\n marginBottom: '12px',\n padding: '12px',\n }}\n >\n <div\n style={{\n alignItems: 'center',\n display: 'flex',\n justifyContent: 'space-between',\n marginBottom: '8px',\n }}\n >\n <strong style={{ color: 'var(--theme-elevation-800)' }}>\n Defaults from AI Settings\n </strong>\n <span\n style={{\n background: 'var(--theme-elevation-100)',\n borderRadius: '10px',\n color: 'var(--theme-elevation-500)',\n fontSize: '11px',\n padding: '2px 8px',\n }}\n >\n Inherited\n </span>\n </div>\n <div\n style={{\n color: 'var(--theme-elevation-600)',\n display: 'flex',\n flexWrap: 'wrap',\n gap: '8px',\n }}\n >\n {Object.entries(configuredDefaults).map(([key, val]) => {\n // Skip nested objects for display\n if (typeof val === 'object' && val !== null) {\n return null\n }\n return (\n <span\n key={key}\n style={{\n background: 'var(--theme-elevation-100)',\n borderRadius: '3px',\n fontSize: '11px',\n padding: '2px 6px',\n }}\n >\n {key}: <strong>{String(val)}</strong>\n </span>\n )\n })}\n </div>\n </div>\n )}\n\n <p\n style={{\n color: 'var(--theme-elevation-500)',\n fontSize: '12px',\n fontStyle: 'italic',\n marginBottom: '12px',\n }}\n >\n Override defaults for this specific field. Empty values inherit from AI Settings.\n </p>\n </div>\n\n <RenderFields\n fields={fields}\n forceRender\n margins=\"small\"\n parentIndexPath=\"\"\n parentPath={path}\n parentSchemaPath={path}\n permissions={true}\n />\n\n {hasOverrides && (\n <button\n onClick={() => setValue({})}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = 'var(--theme-elevation-100)'\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = 'transparent'\n }}\n style={{\n background: 'transparent',\n border: '1px solid var(--theme-elevation-200)',\n borderRadius: '4px',\n color: 'var(--theme-text)',\n cursor: 'pointer',\n fontSize: '13px',\n marginTop: '12px',\n padding: '8px 16px',\n transition: 'all 0.15s ease',\n }}\n type=\"button\"\n >\n Reset to Defaults\n </button>\n )}\n </div>\n )\n}\n"],"names":["RenderFields","useField","useFormFields","React","useEffect","useMemo","useState","allProviderBlocks","findFieldInBlock","block","fieldName","searchFields","fields","field","name","type","tab","tabs","found","undefined","getProviderOptionsFields","providerSlug","useCase","find","b","slug","groupName","optionsGroup","ProviderOptionsEditor","props","path","parentPath","split","slice","join","providerField","provider","value","pathParts","parentName","length","includes","setValue","aiSettings","setAiSettings","fetch","then","res","json","data","catch","err","console","error","configuredDefaults","providerBlock","providers","p","blockType","enabled","optionsKey","hasOverrides","Object","keys","div","className","style","padding","color","fontSize","margin","marginBottom","label","display","background","border","borderRadius","alignItems","justifyContent","strong","span","flexWrap","gap","entries","map","key","val","String","fontStyle","forceRender","margins","parentIndexPath","parentSchemaPath","permissions","button","onClick","onMouseEnter","e","currentTarget","onMouseLeave","cursor","marginTop","transition"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/ui/ProviderOptionsEditor/index.tsx"],"sourcesContent":["'use client'\n\nimport { RenderFields, useField, useFormFields } from '@payloadcms/ui'\nimport React, { useEffect, useMemo, useState } from 'react'\n\nimport { allProviderBlocks } from '../../ai/providers/blocks/index.js'\n\ntype UseCase = 'image' | 'text' | 'tts' | 'video'\n\ninterface ProviderOptionsEditorProps {\n name?: string\n path: string\n}\n\n/**\n * Find a field by name within a block's fields, searching through tabs\n */\nfunction findFieldInBlock(block: any, fieldName: string): any | undefined {\n const searchFields = (fields: any[]): any | undefined => {\n for (const field of fields) {\n if ('name' in field && field.name === fieldName) {\n return field\n }\n if (field.type === 'tabs' && 'tabs' in field) {\n for (const tab of field.tabs) {\n const found = searchFields(tab.fields)\n if (found) {\n return found\n }\n }\n }\n if (field.type === 'group' && 'fields' in field) {\n const found = searchFields(field.fields)\n if (found) {\n return found\n }\n }\n }\n return undefined\n }\n\n return searchFields(block.fields)\n}\n\n/**\n * Get provider options fields for a given provider and use case\n */\nfunction getProviderOptionsFields(providerSlug: string, useCase: UseCase): any[] {\n const block = allProviderBlocks.find((b) => b.slug === providerSlug)\n if (!block) {\n return []\n }\n\n const groupName = `${useCase}ProviderOptions`\n const optionsGroup = findFieldInBlock(block, groupName)\n\n if (optionsGroup && optionsGroup.type === 'group' && 'fields' in optionsGroup) {\n return optionsGroup.fields\n }\n\n return []\n}\n\nexport const ProviderOptionsEditor: React.FC<ProviderOptionsEditorProps> = (props) => {\n const { path } = props\n\n // Get parent path to find sibling provider field\n const parentPath = path.split('.').slice(0, -1).join('.')\n const providerField = useFormFields(([fields]) => fields[`${parentPath}.provider`])\n const provider = providerField?.value as string\n\n // Infer use case from path\n // Handles:\n // - AISettings: 'defaults.text.options' -> useCase is 'text'\n // - Instructions: 'text-settings.providerOptions' -> useCase is 'text'\n const useCase: UseCase = useMemo(() => {\n // Check for AISettings paths first (e.g., 'defaults.text.options')\n const pathParts = path.split('.')\n const parentName = pathParts[pathParts.length - 2]\n\n if (['image', 'text', 'tts', 'video'].includes(parentName)) {\n return parentName as UseCase\n }\n\n // Check for Instructions paths\n if (path.includes('tts-settings')) {\n return 'tts'\n }\n if (path.includes('image-settings')) {\n return 'image'\n }\n if (path.includes('video-settings')) {\n return 'video'\n }\n return 'text'\n }, [path])\n\n const { setValue, value } = useField<Record<string, any>>({ path })\n const [aiSettings, setAiSettings] = useState<any>(null)\n\n // Fetch AI Settings to get current provider defaults\n useEffect(() => {\n fetch('/api/globals/ai-providers?depth=1')\n .then((res) => res.json())\n .then((data) => setAiSettings(data))\n .catch((err) => console.error('Error fetching AI settings:', err))\n }, [])\n\n // Get the configured default options from AI Settings for this provider\n const configuredDefaults = useMemo(() => {\n if (!provider || !aiSettings) {\n return null\n }\n\n const providerBlock = aiSettings.providers?.find(\n (p: any) => p.blockType === provider && p.enabled,\n )\n if (!providerBlock) {\n return null\n }\n\n // Get provider options by use case\n const optionsKey = `${useCase}ProviderOptions`\n return providerBlock[optionsKey] || null\n }, [provider, useCase, aiSettings])\n\n // Get field definitions from provider block\n const fields = useMemo(() => {\n if (!provider) {\n return []\n }\n return getProviderOptionsFields(provider, useCase)\n }, [provider, useCase])\n\n // Check if there are any overrides set\n const hasOverrides = useMemo(() => {\n return value && Object.keys(value).length > 0\n }, [value])\n\n if (!provider) {\n return (\n <div className=\"field-type\" style={{ padding: '12px 0' }}>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px', margin: 0 }}>\n Please select a provider first to configure options.\n </p>\n </div>\n )\n }\n\n if (fields.length === 0) {\n return (\n <div className=\"field-type\" style={{ padding: '12px 0' }}>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px', margin: 0 }}>\n No configurable options available for {provider} ({useCase}).\n </p>\n </div>\n )\n }\n\n return (\n <div className=\"field-type provider-options-editor\">\n <div style={{ marginBottom: '16px' }}>\n <label className=\"field-label\" style={{ display: 'block', marginBottom: '8px' }}>\n Provider Options\n </label>\n\n {configuredDefaults && (\n <div\n style={{\n background: 'var(--theme-elevation-50)',\n border: '1px solid var(--theme-elevation-100)',\n borderRadius: '4px',\n fontSize: '12px',\n marginBottom: '12px',\n padding: '12px',\n }}\n >\n <div\n style={{\n alignItems: 'center',\n display: 'flex',\n justifyContent: 'space-between',\n marginBottom: '8px',\n }}\n >\n <strong style={{ color: 'var(--theme-elevation-800)' }}>\n Defaults from AI Settings\n </strong>\n <span\n style={{\n background: 'var(--theme-elevation-100)',\n borderRadius: '10px',\n color: 'var(--theme-elevation-500)',\n fontSize: '11px',\n padding: '2px 8px',\n }}\n >\n Inherited\n </span>\n </div>\n <div\n style={{\n color: 'var(--theme-elevation-600)',\n display: 'flex',\n flexWrap: 'wrap',\n gap: '8px',\n }}\n >\n {Object.entries(configuredDefaults).map(([key, val]) => {\n // Skip nested objects for display\n if (typeof val === 'object' && val !== null) {\n return null\n }\n return (\n <span\n key={key}\n style={{\n background: 'var(--theme-elevation-100)',\n borderRadius: '3px',\n fontSize: '11px',\n padding: '2px 6px',\n }}\n >\n {key}: <strong>{String(val)}</strong>\n </span>\n )\n })}\n </div>\n </div>\n )}\n\n <p\n style={{\n color: 'var(--theme-elevation-500)',\n fontSize: '12px',\n fontStyle: 'italic',\n marginBottom: '12px',\n }}\n >\n Override defaults for this specific field. Empty values inherit from AI Settings.\n </p>\n </div>\n\n <RenderFields\n fields={fields}\n forceRender\n margins=\"small\"\n parentIndexPath=\"\"\n parentPath={path}\n parentSchemaPath={path}\n permissions={true}\n />\n\n {hasOverrides && (\n <button\n onClick={() => setValue({})}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = 'var(--theme-elevation-100)'\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = 'transparent'\n }}\n style={{\n background: 'transparent',\n border: '1px solid var(--theme-elevation-200)',\n borderRadius: '4px',\n color: 'var(--theme-text)',\n cursor: 'pointer',\n fontSize: '13px',\n marginTop: '12px',\n padding: '8px 16px',\n transition: 'all 0.15s ease',\n }}\n type=\"button\"\n >\n Reset to Defaults\n </button>\n )}\n </div>\n )\n}\n"],"names":["RenderFields","useField","useFormFields","React","useEffect","useMemo","useState","allProviderBlocks","findFieldInBlock","block","fieldName","searchFields","fields","field","name","type","tab","tabs","found","undefined","getProviderOptionsFields","providerSlug","useCase","find","b","slug","groupName","optionsGroup","ProviderOptionsEditor","props","path","parentPath","split","slice","join","providerField","provider","value","pathParts","parentName","length","includes","setValue","aiSettings","setAiSettings","fetch","then","res","json","data","catch","err","console","error","configuredDefaults","providerBlock","providers","p","blockType","enabled","optionsKey","hasOverrides","Object","keys","div","className","style","padding","color","fontSize","margin","marginBottom","label","display","background","border","borderRadius","alignItems","justifyContent","strong","span","flexWrap","gap","entries","map","key","val","String","fontStyle","forceRender","margins","parentIndexPath","parentSchemaPath","permissions","button","onClick","onMouseEnter","e","currentTarget","onMouseLeave","cursor","marginTop","transition"],"mappings":"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"}
@@ -78,7 +78,7 @@ export const ProviderOptionsEditor = (props) => {
78
78
  const [aiSettings, setAiSettings] = useState(null);
79
79
  // Fetch AI Settings to get current provider defaults
80
80
  useEffect(() => {
81
- fetch('/api/globals/ai-settings?depth=1')
81
+ fetch('/api/globals/ai-providers?depth=1')
82
82
  .then((res) => res.json())
83
83
  .then((data) => setAiSettings(data))
84
84
  .catch((err) => console.error('Error fetching AI settings:', err));
@@ -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, 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,41 +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`;
16
+ const { setValue } = useField({
17
+ path: voicesPath
18
+ });
17
19
  const fetchVoices = useCallback(async ()=>{
18
20
  setLoading(true);
21
+ const controller = new AbortController();
22
+ const timeoutId = setTimeout(()=>controller.abort(), 30000) // 30s timeout
23
+ ;
19
24
  try {
20
25
  // Call server endpoint - it will read the API key from the database
21
26
  const response = await fetch(`/api${PLUGIN_API_ENDPOINT_FETCH_VOICES}`, {
22
27
  headers: {
23
28
  'Content-Type': 'application/json'
24
29
  },
25
- method: 'POST'
30
+ method: 'POST',
31
+ signal: controller.signal
26
32
  });
33
+ clearTimeout(timeoutId);
27
34
  if (!response.ok) {
28
- const error = await response.json();
29
- 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);
30
44
  }
31
45
  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!`);
46
+ const voices = data.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!`);
39
51
  } catch (error) {
40
- 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
+ }
41
58
  } finally{
42
59
  setLoading(false);
60
+ clearTimeout(timeoutId);
43
61
  }
44
62
  }, [
45
- dispatchFields,
46
- voicesPath
63
+ setValue
47
64
  ]);
48
65
  return /*#__PURE__*/ _jsxs("div", {
49
66
  style: {
@@ -53,9 +70,10 @@ import { PLUGIN_API_ENDPOINT_FETCH_VOICES } from '../../defaults.js';
53
70
  /*#__PURE__*/ _jsx(Button, {
54
71
  buttonStyle: "secondary",
55
72
  disabled: loading,
73
+ margin: false,
56
74
  onClick: fetchVoices,
57
75
  size: "medium",
58
- children: loading ? 'Fetching Voices...' : 'Fetch Voices from ElevenLabs'
76
+ children: loading ? 'Fetching Voices...' : 'Fetch Voices'
59
77
  }),
60
78
  /*#__PURE__*/ _jsx("p", {
61
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 } 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, 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 } 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,13 +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`;
16
+ const { setValue } = useField({ path: voicesPath });
17
17
  const fetchVoices = useCallback(async () => {
18
18
  setLoading(true);
19
+ const controller = new AbortController();
20
+ const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
19
21
  try {
20
22
  // Call server endpoint - it will read the API key from the database
21
23
  const response = await fetch(`/api${PLUGIN_API_ENDPOINT_FETCH_VOICES}`, {
@@ -23,30 +25,45 @@ export const VoicesFetcher = ({ path }) => {
23
25
  'Content-Type': 'application/json',
24
26
  },
25
27
  method: 'POST',
28
+ signal: controller.signal,
26
29
  });
30
+ clearTimeout(timeoutId);
27
31
  if (!response.ok) {
28
- const error = await response.json();
29
- 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);
30
42
  }
31
43
  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!`);
44
+ const voices = data.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!`);
39
49
  }
40
50
  catch (error) {
41
- 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
+ }
42
58
  }
43
59
  finally {
44
60
  setLoading(false);
61
+ clearTimeout(timeoutId);
45
62
  }
46
- }, [dispatchFields, voicesPath]);
63
+ }, [setValue]);
47
64
  return (<div style={{ marginBottom: '20px' }}>
48
- <Button buttonStyle="secondary" disabled={loading} onClick={fetchVoices} size="medium">
49
- {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'}
50
67
  </Button>
51
68
  <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px', marginTop: '8px' }}>
52
69
  This will fetch all available voices from your ElevenLabs account. Make sure you have saved
@@ -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,141 @@
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(`Field description for user: ${description}\n`);
109
+ }
110
+ parts.push(getTypeGuidance(type, label || name));
111
+ // Add parent context if nested
112
+ const parentPhrase = getParentContextPhrase(parentContext);
113
+ if (parentPhrase) {
114
+ parts.push(parentPhrase);
115
+ }
116
+ // Add document title context if available
117
+ const title = documentData?.title || documentData?.name;
118
+ if (title && typeof title === 'string') {
119
+ parts.push(`in the context of "${title}"`);
120
+ }
121
+ // Build the final prompt
122
+ let prompt = parts.join(' ');
123
+ // Ensure first letter is capitalized
124
+ prompt = prompt.charAt(0).toUpperCase() + prompt.slice(1);
125
+ // Add instruction suffix for clarity
126
+ if (!prompt.endsWith('.')) {
127
+ prompt += '.';
128
+ }
129
+ return prompt;
130
+ };
131
+ /**
132
+ * Check if a prompt template is empty and should be replaced with a smart prompt.
133
+ * Only triggers when the prompt is completely empty or whitespace-only.
134
+ */ export const isGenericPrompt = (template)=>{
135
+ if (!template) {
136
+ return true;
137
+ }
138
+ return template.trim() === '';
139
+ };
140
+
141
+ //# 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) {\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"}
@@ -138,7 +138,7 @@ export function fieldToJsonSchema(fieldInput, opts) {
138
138
  }
139
139
  continue;
140
140
  }
141
- if (!subField.name) {
141
+ if (!subField.name || subField.name === 'id') {
142
142
  continue;
143
143
  }
144
144
  const subSchema = fieldToJsonSchema(subField, {
@@ -146,8 +146,31 @@ export function fieldToJsonSchema(fieldInput, opts) {
146
146
  });
147
147
  if (subSchema && Object.keys(subSchema).length > 0) {
148
148
  properties[subField.name] = subSchema;
149
- if (subField.required) {
150
- required.push(subField.name);
149
+ // OpenAI Strict Mode: All fields must be required
150
+ required.push(subField.name);
151
+ // If field is optional in Payload, allow null in schema
152
+ if (!subField.required) {
153
+ // Arrays usually default to empty array [], so we don't make them nullable
154
+ // Groups and other types can be null
155
+ if (subSchema.type !== 'array') {
156
+ if (Array.isArray(subSchema.type)) {
157
+ if (!subSchema.type.includes('null')) {
158
+ subSchema.type.push('null');
159
+ }
160
+ } else if (typeof subSchema.type === 'string' && subSchema.type !== 'null') {
161
+ subSchema.type = [
162
+ subSchema.type,
163
+ 'null'
164
+ ];
165
+ }
166
+ // If enum is present, we must allow null in enum or use anyOf?
167
+ // OpenAI strict mode: "enum values must be consistent with type".
168
+ // If type is [string, null], null is a valid value for the type, but if enum is ["a"], null is not in enum.
169
+ // valid: { type: ["string", "null"], enum: ["a", null] }
170
+ if (Array.isArray(subSchema.enum) && !subSchema.enum.includes(null)) {
171
+ subSchema.enum.push(null);
172
+ }
173
+ }
151
174
  }
152
175
  }
153
176
  }
@@ -164,6 +187,12 @@ export function fieldToJsonSchema(fieldInput, opts) {
164
187
  type: 'array',
165
188
  items: objSchema
166
189
  };
190
+ if (typeof field.maxRows === 'number') {
191
+ valueSchema.maxItems = field.maxRows;
192
+ }
193
+ if (typeof field.minRows === 'number') {
194
+ valueSchema.minItems = field.minRows;
195
+ }
167
196
  } else {
168
197
  valueSchema = objSchema;
169
198
  }