@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.
- package/dist/ai/core/media/image/generateImage.js +2 -6
- package/dist/ai/core/media/image/generateImage.js.map +1 -1
- package/dist/ai/core/media/image/handlers/multimodal.js +13 -28
- package/dist/ai/core/media/image/handlers/multimodal.js.map +1 -1
- package/dist/ai/core/media/image/handlers/standard.js +10 -7
- package/dist/ai/core/media/image/handlers/standard.js.map +1 -1
- package/dist/ai/core/media/speech/generateSpeech.js +8 -8
- package/dist/ai/core/media/speech/generateSpeech.js.map +1 -1
- package/dist/ai/core/media/types.d.ts +1 -1
- package/dist/ai/core/media/types.js.map +1 -1
- package/dist/ai/core/streamObject.js +3 -3
- package/dist/ai/core/streamObject.js.map +1 -1
- package/dist/ai/core/types.d.ts +3 -0
- package/dist/ai/core/types.js.map +1 -1
- package/dist/ai/prompts.d.ts +1 -2
- package/dist/ai/prompts.js +0 -110
- package/dist/ai/prompts.js.map +1 -1
- package/dist/ai/providers/blocks/anthropic.js +2 -1
- package/dist/ai/providers/blocks/anthropic.js.map +1 -1
- package/dist/ai/providers/blocks/elevenlabs.js +3 -2
- package/dist/ai/providers/blocks/elevenlabs.js.map +1 -1
- package/dist/ai/providers/blocks/fal.js +2 -1
- package/dist/ai/providers/blocks/fal.js.map +1 -1
- package/dist/ai/providers/blocks/google.js +12 -6
- package/dist/ai/providers/blocks/google.js.map +1 -1
- package/dist/ai/providers/blocks/openai-compatible.js +2 -1
- package/dist/ai/providers/blocks/openai-compatible.js.map +1 -1
- package/dist/ai/providers/blocks/openai.js +3 -2
- package/dist/ai/providers/blocks/openai.js.map +1 -1
- package/dist/ai/providers/blocks/xai.js +2 -1
- package/dist/ai/providers/blocks/xai.js.map +1 -1
- package/dist/ai/providers/icons.d.ts +7 -0
- package/dist/ai/providers/icons.js +9 -0
- package/dist/ai/providers/icons.js.map +1 -0
- package/dist/ai/providers/registry.js +36 -26
- package/dist/ai/providers/registry.js.map +1 -1
- package/dist/ai/utils/filterEditorSchemaByNodes.d.ts +9 -0
- package/dist/ai/utils/filterEditorSchemaByNodes.js +30 -3
- package/dist/ai/utils/filterEditorSchemaByNodes.js.map +1 -1
- package/dist/ai/utils/nodeToSchemaMap.d.ts +22 -0
- package/dist/ai/utils/nodeToSchemaMap.js +72 -0
- package/dist/ai/utils/nodeToSchemaMap.js.map +1 -0
- package/dist/collections/AIJobs.js +1 -1
- package/dist/collections/AIJobs.js.map +1 -1
- package/dist/collections/AIProviders.d.ts +2 -0
- package/dist/collections/{AISettings.js → AIProviders.js} +53 -25
- package/dist/collections/AIProviders.js.map +1 -0
- package/dist/collections/Instructions.js +45 -4
- package/dist/collections/Instructions.js.map +1 -1
- package/dist/defaults.d.ts +1 -0
- package/dist/defaults.js +8 -0
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/chat.d.ts +4 -0
- package/dist/endpoints/fetchFields.js +10 -0
- package/dist/endpoints/fetchFields.js.map +1 -1
- package/dist/endpoints/fetchVoices.js +42 -25
- package/dist/endpoints/fetchVoices.js.map +1 -1
- package/dist/endpoints/index.js +248 -35
- package/dist/endpoints/index.js.map +1 -1
- package/dist/exports/client.d.ts +1 -1
- package/dist/exports/client.js +1 -1
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/fields.d.ts +1 -0
- package/dist/exports/fields.js +1 -0
- package/dist/exports/fields.js.map +1 -1
- package/dist/fields/ArrayComposeField/ArrayComposeField.d.ts +15 -0
- package/dist/fields/ArrayComposeField/ArrayComposeField.js +87 -0
- package/dist/fields/ArrayComposeField/ArrayComposeField.js.map +1 -0
- package/dist/fields/ArrayComposeField/ArrayComposeField.jsx +73 -0
- package/dist/fields/PromptEditorField/PromptEditorField.js +7 -2
- package/dist/fields/PromptEditorField/PromptEditorField.js.map +1 -1
- package/dist/fields/PromptEditorField/PromptEditorField.jsx +5 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/payload-ai.d.ts +152 -0
- package/dist/plugin.js +18 -34
- package/dist/plugin.js.map +1 -1
- package/dist/providers/InstructionsProvider/InstructionsProvider.js +47 -15
- package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
- package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +39 -16
- package/dist/providers/InstructionsProvider/context.d.ts +3 -0
- package/dist/providers/InstructionsProvider/context.js +2 -0
- package/dist/providers/InstructionsProvider/context.js.map +1 -1
- package/dist/providers/InstructionsProvider/useInstructions.js +22 -3
- package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
- package/dist/styles.d.ts +11 -0
- package/dist/types/handlebars-async-helpers.d.ts +1 -0
- package/dist/types/handlebars-dist-handlebars.d.ts +1 -0
- package/dist/types/react-mentions.d.ts +1 -0
- package/dist/types.d.ts +36 -7
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/ui/AIConfigDashboard/index.d.ts +1 -1
- package/dist/ui/AIConfigDashboard/index.js +201 -23
- package/dist/ui/AIConfigDashboard/index.js.map +1 -1
- package/dist/ui/AIConfigDashboard/index.jsx +166 -15
- package/dist/ui/Compose/Compose.d.ts +1 -0
- package/dist/ui/Compose/Compose.js +23 -4
- package/dist/ui/Compose/Compose.js.map +1 -1
- package/dist/ui/Compose/Compose.jsx +23 -4
- package/dist/ui/Compose/UndoRedoActions.d.ts +2 -2
- package/dist/ui/Compose/UndoRedoActions.js +8 -5
- package/dist/ui/Compose/UndoRedoActions.js.map +1 -1
- package/dist/ui/Compose/UndoRedoActions.jsx +6 -5
- package/dist/ui/Compose/compose.module.css +56 -16
- package/dist/ui/Compose/hooks/menu/TranslateMenu.d.ts +5 -0
- package/dist/ui/Compose/hooks/menu/TranslateMenu.js +45 -4
- package/dist/ui/Compose/hooks/menu/TranslateMenu.js.map +1 -1
- package/dist/ui/Compose/hooks/menu/TranslateMenu.jsx +41 -5
- package/dist/ui/Compose/hooks/menu/itemsMap.js +12 -6
- package/dist/ui/Compose/hooks/menu/itemsMap.js.map +1 -1
- package/dist/ui/Compose/hooks/menu/menu.module.scss +4 -1
- package/dist/ui/Compose/hooks/menu/useMenu.js +26 -15
- package/dist/ui/Compose/hooks/menu/useMenu.js.map +1 -1
- package/dist/ui/Compose/hooks/menu/useMenu.jsx +25 -12
- package/dist/ui/Compose/hooks/useActiveFieldTracking.js +34 -0
- package/dist/ui/Compose/hooks/useActiveFieldTracking.js.map +1 -1
- package/dist/ui/Compose/hooks/useGenerate.js +26 -174
- package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
- package/dist/ui/Compose/hooks/useGenerateUpload.d.ts +11 -0
- package/dist/ui/Compose/hooks/useGenerateUpload.js +156 -0
- package/dist/ui/Compose/hooks/useGenerateUpload.js.map +1 -0
- package/dist/ui/Compose/hooks/useHistory.d.ts +0 -1
- package/dist/ui/Compose/hooks/useHistory.js +65 -25
- package/dist/ui/Compose/hooks/useHistory.js.map +1 -1
- package/dist/ui/Compose/hooks/useStreamingUpdate.d.ts +8 -0
- package/dist/ui/Compose/hooks/useStreamingUpdate.js +48 -0
- package/dist/ui/Compose/hooks/useStreamingUpdate.js.map +1 -0
- package/dist/ui/ConfigDashboard/index.d.ts +2 -0
- package/dist/ui/ConfigDashboard/index.js +224 -0
- package/dist/ui/ConfigDashboard/index.js.map +1 -0
- package/dist/ui/ConfigDashboard/index.jsx +175 -0
- package/dist/ui/DynamicModelSelect/index.js +1 -1
- package/dist/ui/DynamicModelSelect/index.js.map +1 -1
- package/dist/ui/DynamicModelSelect/index.jsx +1 -1
- package/dist/ui/DynamicProviderSelect/index.js +1 -1
- package/dist/ui/DynamicProviderSelect/index.js.map +1 -1
- package/dist/ui/DynamicProviderSelect/index.jsx +1 -1
- package/dist/ui/DynamicVoiceSelect/index.js +63 -11
- package/dist/ui/DynamicVoiceSelect/index.js.map +1 -1
- package/dist/ui/DynamicVoiceSelect/index.jsx +47 -14
- package/dist/ui/EncryptedTextField/index.js +4 -4
- package/dist/ui/EncryptedTextField/index.js.map +1 -1
- package/dist/ui/EncryptedTextField/index.jsx +4 -4
- package/dist/ui/ProviderOptionsEditor/index.js +1 -1
- package/dist/ui/ProviderOptionsEditor/index.js.map +1 -1
- package/dist/ui/ProviderOptionsEditor/index.jsx +1 -1
- package/dist/ui/VoicesFetcher/index.js +34 -16
- package/dist/ui/VoicesFetcher/index.js.map +1 -1
- package/dist/ui/VoicesFetcher/index.jsx +32 -15
- package/dist/utilities/buildSmartPrompt.d.ts +22 -0
- package/dist/utilities/buildSmartPrompt.js +141 -0
- package/dist/utilities/buildSmartPrompt.js.map +1 -0
- package/dist/utilities/encryption.js +2 -1
- package/dist/utilities/encryption.js.map +1 -1
- package/dist/utilities/fieldToJsonSchema.js +32 -3
- package/dist/utilities/fieldToJsonSchema.js.map +1 -1
- package/dist/utilities/resolveImageReferences.d.ts +3 -1
- package/dist/utilities/resolveImageReferences.js +21 -2
- package/dist/utilities/resolveImageReferences.js.map +1 -1
- package/dist/utilities/seedProperties.d.ts +7 -0
- package/dist/utilities/seedProperties.js +100 -0
- package/dist/utilities/seedProperties.js.map +1 -0
- package/dist/utilities/setSafeLexicalState.js +79 -6
- package/dist/utilities/setSafeLexicalState.js.map +1 -1
- package/dist/utilities/updateFieldsConfig.d.ts +1 -1
- package/dist/utilities/updateFieldsConfig.js +9 -2
- package/dist/utilities/updateFieldsConfig.js.map +1 -1
- package/package.json +35 -33
- package/dist/collections/AISettings.d.ts +0 -2
- package/dist/collections/AISettings.js.map +0 -1
- package/dist/endpoints/chat.d.js +0 -3
- package/dist/endpoints/chat.d.js.map +0 -1
- package/dist/init.d.ts +0 -7
- package/dist/init.js +0 -135
- package/dist/init.js.map +0 -1
- package/dist/payload-ai.d.js +0 -3
- package/dist/payload-ai.d.js.map +0 -1
- package/dist/styles.d.js +0 -2
- package/dist/styles.d.js.map +0 -1
- package/dist/types/handlebars-async-helpers.d.js +0 -2
- package/dist/types/handlebars-async-helpers.d.js.map +0 -1
- package/dist/types/handlebars-dist-handlebars.d.js +0 -2
- package/dist/types/handlebars-dist-handlebars.d.js.map +0 -1
- package/dist/types/react-mentions.d.js +0 -2
- 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
|
-
<
|
|
27
|
+
<Button buttonStyle="secondary" onClick={() => {
|
|
28
28
|
setValue('');
|
|
29
29
|
setIsEditing(true);
|
|
30
|
-
}}
|
|
30
|
+
}} size="medium">
|
|
31
31
|
Change
|
|
32
|
-
</
|
|
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-
|
|
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-
|
|
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,
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
150
|
-
|
|
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
|
}
|