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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) 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/providers/blocks/elevenlabs.js +1 -1
  4. package/dist/ai/providers/blocks/elevenlabs.js.map +1 -1
  5. package/dist/ai/providers/blocks/google.js +9 -5
  6. package/dist/ai/providers/blocks/google.js.map +1 -1
  7. package/dist/ai/providers/blocks/openai.js +1 -1
  8. package/dist/ai/providers/blocks/openai.js.map +1 -1
  9. package/dist/collections/AISettings.js +44 -17
  10. package/dist/collections/AISettings.js.map +1 -1
  11. package/dist/defaults.d.ts +1 -0
  12. package/dist/defaults.js +8 -0
  13. package/dist/defaults.js.map +1 -1
  14. package/dist/endpoints/fetchFields.js +10 -0
  15. package/dist/endpoints/fetchFields.js.map +1 -1
  16. package/dist/plugin.js +16 -32
  17. package/dist/plugin.js.map +1 -1
  18. package/dist/providers/InstructionsProvider/InstructionsProvider.js +44 -15
  19. package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
  20. package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +36 -16
  21. package/dist/providers/InstructionsProvider/context.d.ts +3 -0
  22. package/dist/providers/InstructionsProvider/context.js +2 -0
  23. package/dist/providers/InstructionsProvider/context.js.map +1 -1
  24. package/dist/providers/InstructionsProvider/useInstructions.js +3 -1
  25. package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
  26. package/dist/types.d.ts +0 -3
  27. package/dist/types.js.map +1 -1
  28. package/dist/ui/AIConfigDashboard/index.js +198 -22
  29. package/dist/ui/AIConfigDashboard/index.js.map +1 -1
  30. package/dist/ui/AIConfigDashboard/index.jsx +159 -13
  31. package/dist/ui/Compose/Compose.js +5 -3
  32. package/dist/ui/Compose/Compose.js.map +1 -1
  33. package/dist/ui/Compose/Compose.jsx +3 -3
  34. package/dist/ui/Compose/hooks/useGenerate.js +34 -131
  35. package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
  36. package/dist/ui/Compose/hooks/useGenerateUpload.d.ts +11 -0
  37. package/dist/ui/Compose/hooks/useGenerateUpload.js +123 -0
  38. package/dist/ui/Compose/hooks/useGenerateUpload.js.map +1 -0
  39. package/dist/ui/Compose/hooks/useStreamingUpdate.d.ts +8 -0
  40. package/dist/ui/Compose/hooks/useStreamingUpdate.js +48 -0
  41. package/dist/ui/Compose/hooks/useStreamingUpdate.js.map +1 -0
  42. package/dist/ui/EncryptedTextField/index.js +4 -4
  43. package/dist/ui/EncryptedTextField/index.js.map +1 -1
  44. package/dist/ui/EncryptedTextField/index.jsx +4 -4
  45. package/dist/utilities/seedProperties.d.ts +7 -0
  46. package/dist/utilities/seedProperties.js +117 -0
  47. package/dist/utilities/seedProperties.js.map +1 -0
  48. package/dist/utilities/setSafeLexicalState.js +80 -6
  49. package/dist/utilities/setSafeLexicalState.js.map +1 -1
  50. package/dist/utilities/updateFieldsConfig.d.ts +1 -1
  51. package/dist/utilities/updateFieldsConfig.js +1 -0
  52. package/dist/utilities/updateFieldsConfig.js.map +1 -1
  53. package/package.json +2 -1
  54. package/dist/init.d.ts +0 -7
  55. package/dist/init.js +0 -152
  56. package/dist/init.js.map +0 -1
@@ -1,8 +1,8 @@
1
- import { InstructionsContext } from '@ai-stack/payloadcms/client';
2
1
  import { useDocumentInfo } from '@payloadcms/ui';
3
2
  import { useContext, useEffect, useMemo, useState } from 'react';
4
3
  import { PLUGIN_INSTRUCTIONS_TABLE } from '../../defaults.js';
5
4
  import { handlebarsHelpers, handlebarsHelpersMap } from '../../libraries/handlebars/helpersMap.js';
5
+ import { InstructionsContext } from './context.js';
6
6
  /**
7
7
  * Normalize a schema path by removing array indices.
8
8
  * This allows fields inside arrays to match their instruction records.
@@ -112,9 +112,11 @@ export const useInstructions = (update = {})=>{
112
112
  if (debugging && !pathInstructions && schemaPath && hasInstructions) {
113
113
  warnOnceOnMissingInstructions(schemaPath);
114
114
  }
115
+ const isCollectionEnabled = context.enabledCollections?.includes(collectionSlug || activeCollection || '') ?? false;
115
116
  return {
116
117
  ...context,
117
118
  ...pathInstructions || {},
119
+ disabled: !isCollectionEnabled || (pathInstructions?.disabled ?? false),
118
120
  promptEditorSuggestions
119
121
  };
120
122
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/providers/InstructionsProvider/useInstructions.ts"],"sourcesContent":["import { InstructionsContext } from '@ai-stack/payloadcms/client'\nimport { useDocumentInfo } from '@payloadcms/ui'\nimport { useContext, useEffect, useMemo, useState } from 'react'\n\nimport { PLUGIN_INSTRUCTIONS_TABLE } from '../../defaults.js'\nimport { handlebarsHelpers, handlebarsHelpersMap } from '../../libraries/handlebars/helpersMap.js'\n\n/**\n * Normalize a schema path by removing array indices.\n * This allows fields inside arrays to match their instruction records.\n * \n * Example:\n * 'array-test-cases.keywords.0.keyword' -> 'array-test-cases.keywords.keyword'\n * 'characters.views.2.description' -> 'characters.views.description'\n * 'posts.title' -> 'posts.title' (no change)\n */\nconst normalizeSchemaPath = (path: string): string => {\n if (!path) {\n return path\n }\n // Remove numeric path segments (array indices)\n return path.split('.').filter(segment => !/^\\d+$/.test(segment)).join('.')\n}\n\nconst warnedOnceOnNoInstructionId = new Set<string>()\nconst warnOnceOnMissingInstructions = (path: string) => {\n if (!warnedOnceOnNoInstructionId.has(path)) {\n warnedOnceOnNoInstructionId.add(path)\n // eslint-disable-next-line no-console\n console.info(`[AI Plugin] There are no AI instructions for this field: ${path}. Enable \"generatePromptOnInit\" option to enable them.`)\n }\n}\n\nexport const useInstructions = (\n update: {\n schemaPath?: unknown\n } = {},\n) => {\n const context = useContext(InstructionsContext)\n const { collectionSlug } = useDocumentInfo()\n const { activeCollection, debugging, hasInstructions, instructions, promptFields, setActiveCollection } = context\n\n const [schemaPath, setSchemaPath] = useState(update.schemaPath as string)\n\n useEffect(() => {\n if (update.schemaPath !== schemaPath) {\n setSchemaPath((update.schemaPath as string) ?? '')\n }\n }, [update.schemaPath, schemaPath])\n\n useEffect(() => {\n if (\n activeCollection !== collectionSlug &&\n collectionSlug !== PLUGIN_INSTRUCTIONS_TABLE &&\n typeof setActiveCollection === 'function'\n ) {\n setActiveCollection(collectionSlug ?? '')\n }\n }, [activeCollection, collectionSlug, setActiveCollection])\n\n const groupedFields = useMemo(() => {\n const result: Record<string, string[]> = {}\n\n for (const fullKey of Object.keys(instructions || {})) {\n const [collection, ...pathParts] = fullKey.split('.')\n const path = pathParts.join('.')\n if (!result[collection]) {\n result[collection] = []\n }\n result[collection].push(path)\n }\n\n return result\n }, [instructions])\n\n // Suggestions for prompt editor\n const promptEditorSuggestions = useMemo(() => {\n const activeFields = groupedFields[activeCollection as string] || []\n const suggestions: string[] = []\n\n // Build instruction lookup map once for O(1) access instead of O(n) search per field\n const instructionLookup = new Map(\n Object.entries(instructions).map(([key, value]) => {\n const path = key.split('.').slice(1).join('.')\n return [path, value]\n })\n )\n\n activeFields.forEach((f) => {\n const fieldInfo = instructionLookup.get(f)\n\n if (!fieldInfo) {return}\n\n if (fieldInfo.fieldType === 'upload') {\n return\n }\n\n const helpers = handlebarsHelpers.filter(\n (h) => (handlebarsHelpersMap as Record<string, { field?: string }>)[h]?.field === fieldInfo.fieldType,\n )\n\n if (helpers.length) {\n for (const helper of helpers) {\n suggestions.push(`${helper} ${f}`)\n }\n } else {\n suggestions.push(f)\n }\n })\n\n promptFields.forEach(({ name, collections }) => {\n if (!activeCollection) {return}\n\n if (!collections || collections.includes(activeCollection)) {\n suggestions.push(name)\n }\n })\n\n return suggestions\n }, [groupedFields, activeCollection, instructions, promptFields])\n\n // Normalize the schema path to handle array indices\n const normalizedSchemaPath = normalizeSchemaPath(schemaPath)\n const pathInstructions = instructions[normalizedSchemaPath]\n\n if (debugging && !pathInstructions && schemaPath && hasInstructions) {\n warnOnceOnMissingInstructions(schemaPath)\n }\n \n return {\n ...context,\n ...(pathInstructions || {}),\n promptEditorSuggestions,\n }\n}\n\n"],"names":["InstructionsContext","useDocumentInfo","useContext","useEffect","useMemo","useState","PLUGIN_INSTRUCTIONS_TABLE","handlebarsHelpers","handlebarsHelpersMap","normalizeSchemaPath","path","split","filter","segment","test","join","warnedOnceOnNoInstructionId","Set","warnOnceOnMissingInstructions","has","add","console","info","useInstructions","update","context","collectionSlug","activeCollection","debugging","hasInstructions","instructions","promptFields","setActiveCollection","schemaPath","setSchemaPath","groupedFields","result","fullKey","Object","keys","collection","pathParts","push","promptEditorSuggestions","activeFields","suggestions","instructionLookup","Map","entries","map","key","value","slice","forEach","f","fieldInfo","get","fieldType","helpers","h","field","length","helper","name","collections","includes","normalizedSchemaPath","pathInstructions"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/providers/InstructionsProvider/useInstructions.ts"],"sourcesContent":["import { useDocumentInfo } from '@payloadcms/ui'\nimport { useContext, useEffect, useMemo, useState } from 'react'\n\nimport { PLUGIN_INSTRUCTIONS_TABLE } from '../../defaults.js'\nimport { handlebarsHelpers, handlebarsHelpersMap } from '../../libraries/handlebars/helpersMap.js'\nimport { InstructionsContext } from './context.js'\n\n/**\n * Normalize a schema path by removing array indices.\n * This allows fields inside arrays to match their instruction records.\n * \n * Example:\n * 'array-test-cases.keywords.0.keyword' -> 'array-test-cases.keywords.keyword'\n * 'characters.views.2.description' -> 'characters.views.description'\n * 'posts.title' -> 'posts.title' (no change)\n */\nconst normalizeSchemaPath = (path: string): string => {\n if (!path) {\n return path\n }\n // Remove numeric path segments (array indices)\n return path.split('.').filter(segment => !/^\\d+$/.test(segment)).join('.')\n}\n\nconst warnedOnceOnNoInstructionId = new Set<string>()\nconst warnOnceOnMissingInstructions = (path: string) => {\n if (!warnedOnceOnNoInstructionId.has(path)) {\n warnedOnceOnNoInstructionId.add(path)\n // eslint-disable-next-line no-console\n console.info(`[AI Plugin] There are no AI instructions for this field: ${path}. Enable \"generatePromptOnInit\" option to enable them.`)\n }\n}\n\nexport const useInstructions = (\n update: {\n schemaPath?: unknown\n } = {},\n) => {\n const context = useContext(InstructionsContext)\n const { collectionSlug } = useDocumentInfo()\n const { activeCollection, debugging, hasInstructions, instructions, promptFields, setActiveCollection } = context\n\n const [schemaPath, setSchemaPath] = useState(update.schemaPath as string)\n\n useEffect(() => {\n if (update.schemaPath !== schemaPath) {\n setSchemaPath((update.schemaPath as string) ?? '')\n }\n }, [update.schemaPath, schemaPath])\n\n useEffect(() => {\n if (\n activeCollection !== collectionSlug &&\n collectionSlug !== PLUGIN_INSTRUCTIONS_TABLE &&\n typeof setActiveCollection === 'function'\n ) {\n setActiveCollection(collectionSlug ?? '')\n }\n }, [activeCollection, collectionSlug, setActiveCollection])\n\n const groupedFields = useMemo(() => {\n const result: Record<string, string[]> = {}\n\n for (const fullKey of Object.keys(instructions || {})) {\n const [collection, ...pathParts] = fullKey.split('.')\n const path = pathParts.join('.')\n if (!result[collection]) {\n result[collection] = []\n }\n result[collection].push(path)\n }\n\n return result\n }, [instructions])\n\n // Suggestions for prompt editor\n const promptEditorSuggestions = useMemo(() => {\n const activeFields = groupedFields[activeCollection as string] || []\n const suggestions: string[] = []\n\n // Build instruction lookup map once for O(1) access instead of O(n) search per field\n const instructionLookup = new Map(\n Object.entries(instructions).map(([key, value]) => {\n const path = key.split('.').slice(1).join('.')\n return [path, value]\n })\n )\n\n activeFields.forEach((f) => {\n const fieldInfo = instructionLookup.get(f)\n\n if (!fieldInfo) {return}\n\n if (fieldInfo.fieldType === 'upload') {\n return\n }\n\n const helpers = handlebarsHelpers.filter(\n (h) => (handlebarsHelpersMap as Record<string, { field?: string }>)[h]?.field === fieldInfo.fieldType,\n )\n\n if (helpers.length) {\n for (const helper of helpers) {\n suggestions.push(`${helper} ${f}`)\n }\n } else {\n suggestions.push(f)\n }\n })\n\n promptFields.forEach(({ name, collections }) => {\n if (!activeCollection) {return}\n\n if (!collections || collections.includes(activeCollection)) {\n suggestions.push(name)\n }\n })\n\n return suggestions\n }, [groupedFields, activeCollection, instructions, promptFields])\n\n // Normalize the schema path to handle array indices\n const normalizedSchemaPath = normalizeSchemaPath(schemaPath)\n const pathInstructions = instructions[normalizedSchemaPath]\n\n if (debugging && !pathInstructions && schemaPath && hasInstructions) {\n warnOnceOnMissingInstructions(schemaPath)\n }\n\n const isCollectionEnabled = context.enabledCollections?.includes(collectionSlug || activeCollection || '') ?? false\n\n return {\n ...context,\n ...(pathInstructions || {}),\n disabled: !isCollectionEnabled || (pathInstructions?.disabled ?? false),\n promptEditorSuggestions,\n }\n}\n\n"],"names":["useDocumentInfo","useContext","useEffect","useMemo","useState","PLUGIN_INSTRUCTIONS_TABLE","handlebarsHelpers","handlebarsHelpersMap","InstructionsContext","normalizeSchemaPath","path","split","filter","segment","test","join","warnedOnceOnNoInstructionId","Set","warnOnceOnMissingInstructions","has","add","console","info","useInstructions","update","context","collectionSlug","activeCollection","debugging","hasInstructions","instructions","promptFields","setActiveCollection","schemaPath","setSchemaPath","groupedFields","result","fullKey","Object","keys","collection","pathParts","push","promptEditorSuggestions","activeFields","suggestions","instructionLookup","Map","entries","map","key","value","slice","forEach","f","fieldInfo","get","fieldType","helpers","h","field","length","helper","name","collections","includes","normalizedSchemaPath","pathInstructions","isCollectionEnabled","enabledCollections","disabled"],"mappings":"AAAA,SAASA,eAAe,QAAQ,iBAAgB;AAChD,SAASC,UAAU,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAEhE,SAASC,yBAAyB,QAAQ,oBAAmB;AAC7D,SAASC,iBAAiB,EAAEC,oBAAoB,QAAQ,2CAA0C;AAClG,SAASC,mBAAmB,QAAQ,eAAc;AAElD;;;;;;;;CAQC,GACD,MAAMC,sBAAsB,CAACC;IAC3B,IAAI,CAACA,MAAM;QACT,OAAOA;IACT;IACA,+CAA+C;IAC/C,OAAOA,KAAKC,KAAK,CAAC,KAAKC,MAAM,CAACC,CAAAA,UAAW,CAAC,QAAQC,IAAI,CAACD,UAAUE,IAAI,CAAC;AACxE;AAEA,MAAMC,8BAA8B,IAAIC;AACxC,MAAMC,gCAAgC,CAACR;IACrC,IAAI,CAACM,4BAA4BG,GAAG,CAACT,OAAO;QAC1CM,4BAA4BI,GAAG,CAACV;QAChC,sCAAsC;QACtCW,QAAQC,IAAI,CAAC,CAAC,yDAAyD,EAAEZ,KAAK,sDAAsD,CAAC;IACvI;AACF;AAEA,OAAO,MAAMa,kBAAkB,CAC7BC,SAEI,CAAC,CAAC;IAEN,MAAMC,UAAUxB,WAAWO;IAC3B,MAAM,EAAEkB,cAAc,EAAE,GAAG1B;IAC3B,MAAM,EAAE2B,gBAAgB,EAAEC,SAAS,EAAEC,eAAe,EAAEC,YAAY,EAAEC,YAAY,EAAEC,mBAAmB,EAAE,GAAGP;IAE1G,MAAM,CAACQ,YAAYC,cAAc,GAAG9B,SAASoB,OAAOS,UAAU;IAE9D/B,UAAU;QACR,IAAIsB,OAAOS,UAAU,KAAKA,YAAY;YACpCC,cAAc,AAACV,OAAOS,UAAU,IAAe;QACjD;IACF,GAAG;QAACT,OAAOS,UAAU;QAAEA;KAAW;IAElC/B,UAAU;QACR,IACEyB,qBAAqBD,kBACrBA,mBAAmBrB,6BACnB,OAAO2B,wBAAwB,YAC/B;YACAA,oBAAoBN,kBAAkB;QACxC;IACF,GAAG;QAACC;QAAkBD;QAAgBM;KAAoB;IAE1D,MAAMG,gBAAgBhC,QAAQ;QAC5B,MAAMiC,SAAmC,CAAC;QAE1C,KAAK,MAAMC,WAAWC,OAAOC,IAAI,CAACT,gBAAgB,CAAC,GAAI;YACrD,MAAM,CAACU,YAAY,GAAGC,UAAU,GAAGJ,QAAQ1B,KAAK,CAAC;YACjD,MAAMD,OAAO+B,UAAU1B,IAAI,CAAC;YAC5B,IAAI,CAACqB,MAAM,CAACI,WAAW,EAAE;gBACvBJ,MAAM,CAACI,WAAW,GAAG,EAAE;YACzB;YACAJ,MAAM,CAACI,WAAW,CAACE,IAAI,CAAChC;QAC1B;QAEA,OAAO0B;IACT,GAAG;QAACN;KAAa;IAEjB,gCAAgC;IAChC,MAAMa,0BAA0BxC,QAAQ;QACtC,MAAMyC,eAAeT,aAAa,CAACR,iBAA2B,IAAI,EAAE;QACpE,MAAMkB,cAAwB,EAAE;QAEhC,qFAAqF;QACrF,MAAMC,oBAAoB,IAAIC,IAC5BT,OAAOU,OAAO,CAAClB,cAAcmB,GAAG,CAAC,CAAC,CAACC,KAAKC,MAAM;YAC5C,MAAMzC,OAAOwC,IAAIvC,KAAK,CAAC,KAAKyC,KAAK,CAAC,GAAGrC,IAAI,CAAC;YAC1C,OAAO;gBAACL;gBAAMyC;aAAM;QACtB;QAGFP,aAAaS,OAAO,CAAC,CAACC;YACpB,MAAMC,YAAYT,kBAAkBU,GAAG,CAACF;YAExC,IAAI,CAACC,WAAW;gBAAC;YAAM;YAEvB,IAAIA,UAAUE,SAAS,KAAK,UAAU;gBACpC;YACF;YAEA,MAAMC,UAAUpD,kBAAkBM,MAAM,CACtC,CAAC+C,IAAM,AAACpD,oBAA2D,CAACoD,EAAE,EAAEC,UAAUL,UAAUE,SAAS;YAGvG,IAAIC,QAAQG,MAAM,EAAE;gBAClB,KAAK,MAAMC,UAAUJ,QAAS;oBAC5Bb,YAAYH,IAAI,CAAC,CAAC,EAAEoB,OAAO,CAAC,EAAER,EAAE,CAAC;gBACnC;YACF,OAAO;gBACLT,YAAYH,IAAI,CAACY;YACnB;QACF;QAEAvB,aAAasB,OAAO,CAAC,CAAC,EAAEU,IAAI,EAAEC,WAAW,EAAE;YACzC,IAAI,CAACrC,kBAAkB;gBAAC;YAAM;YAE9B,IAAI,CAACqC,eAAeA,YAAYC,QAAQ,CAACtC,mBAAmB;gBAC1DkB,YAAYH,IAAI,CAACqB;YACnB;QACF;QAEA,OAAOlB;IACT,GAAG;QAACV;QAAeR;QAAkBG;QAAcC;KAAa;IAEhE,oDAAoD;IACpD,MAAMmC,uBAAuBzD,oBAAoBwB;IACjD,MAAMkC,mBAAmBrC,YAAY,CAACoC,qBAAqB;IAE3D,IAAItC,aAAa,CAACuC,oBAAoBlC,cAAcJ,iBAAiB;QACnEX,8BAA8Be;IAChC;IAEA,MAAMmC,sBAAsB3C,QAAQ4C,kBAAkB,EAAEJ,SAASvC,kBAAkBC,oBAAoB,OAAO;IAE9G,OAAO;QACL,GAAGF,OAAO;QACV,GAAI0C,oBAAoB,CAAC,CAAC;QAC1BG,UAAU,CAACF,uBAAwBD,CAAAA,kBAAkBG,YAAY,KAAI;QACrE3B;IACF;AACF,EAAC"}
package/dist/types.d.ts CHANGED
@@ -39,9 +39,6 @@ export interface PluginConfig {
39
39
  * By default, all AI features require authentication
40
40
  */
41
41
  access?: PluginConfigAccess;
42
- collections: {
43
- [key: CollectionSlug]: boolean;
44
- };
45
42
  debugging?: boolean;
46
43
  disableSponsorMessage?: boolean;
47
44
  editorConfig?: {
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { JSONSchema } from 'openai/lib/jsonschema'\nimport type { ImageGenerateParams } from 'openai/resources/images'\nimport type {\n CollectionConfig,\n CollectionSlug,\n DataFromCollectionSlug,\n Endpoint,\n Field,\n File,\n GlobalConfig,\n GroupField,\n PayloadRequest,\n TypedCollection,\n} from 'payload'\nimport type { CSSProperties, MouseEventHandler } from 'react'\n\nimport type { MediaResult } from './ai/core/index.js'\nimport type {PLUGIN_INSTRUCTIONS_TABLE} from \"./defaults.js\";\n\nexport interface PluginConfigAccess {\n /**\n * Control access to AI generation features (generate text, images, audio)\n * @default () => !!req.user (requires authentication)\n */\n generate?: ({ req }: { req: PayloadRequest }) => boolean | Promise<boolean>\n /**\n * Control access to AI settings/configuration\n * @default () => !!req.user (requires authentication)\n */\n settings?: ({ req }: { req: PayloadRequest }) => boolean | Promise<boolean>\n}\n\nexport interface PluginOptions {\n /**\n * Provide local tags to filter language options from the Translate Menu\n * Check for the available local tags,\n * visit: https://www.npmjs.com/package/locale-codes\n * Example: [\"en-US\", \"zh-SG\", \"zh-CN\", \"en\"]\n */\n enabledLanguages?: string[]\n}\n\nexport type PluginConfigMediaUploadFunction = (\n result: MediaResult,\n {\n collection,\n request,\n }: {\n collection: CollectionSlug\n request: PayloadRequest\n },\n) => Promise<DataFromCollectionSlug<CollectionSlug>>\n\nexport interface PluginConfig {\n /**\n * Access control configuration for AI features\n * By default, all AI features require authentication\n */\n access?: PluginConfigAccess\n collections: {\n [key: CollectionSlug]: boolean\n }\n debugging?: boolean\n disableSponsorMessage?: boolean\n editorConfig?: { nodes: JSONSchema[] }\n fields?: Field[]\n generatePromptOnInit?: boolean\n generationModels?: ((defaultModels: GenerationModel[]) => GenerationModel[]) | GenerationModel[]\n globals?: {\n [key: GlobalConfig['slug']]: boolean\n }\n interfaceName?: string\n mediaUpload?: PluginConfigMediaUploadFunction\n options?: PluginOptions\n // Override the instructions collection config\n overrideInstructions?: Partial<CollectionConfig>\n promptFields?: PromptField[]\n /**\n * Custom action prompts for AI text generation\n * If not provided, uses default prompts\n * You can access default prompts by importing { defaultPrompts } from '@ai-stack/payloadcms'\n */\n prompts?: ActionPrompt[]\n /**\n * Custom seed prompt function for generating field-specific prompts\n * If not provided, uses default seed prompt function\n * You can access default seed prompts by importing { defaultSeedPrompts } from '@ai-stack/payloadcms'\n */\n seedPrompts?: SeedPromptFunction\n uploadCollectionSlug?: CollectionSlug\n}\n\nexport interface GenerationModel {\n fields: string[]\n generateText?: (prompt: string, system: string) => Promise<string>\n handler?: (prompt: string, options: any) => File | Promise<any> | Response\n id: string\n name: string\n output: 'audio' | 'file' | 'image' | 'json' | 'text' | 'video'\n settings?: GroupField\n supportsPromptOptimization?: boolean\n}\n\nexport interface GenerationConfig {\n models: GenerationModel[]\n provider: string\n}\n\nexport type GenerateTextarea<T = any> = (args: {\n collectionSlug: CollectionSlug\n doc: T\n documentId?: number | string\n locale?: string\n options?: any\n}) => Promise<string> | string\n\nexport interface Endpoints {\n fetchVoices?: Omit<Endpoint, 'root'>\n textarea: Omit<Endpoint, 'root'>\n upload: Omit<Endpoint, 'root'>\n videogenWebhook?: Omit<Endpoint, 'root'>\n}\n\nexport type ActionMenuItems =\n | 'Compose'\n | 'Expand'\n | 'Proofread'\n | 'Rephrase'\n | 'Settings'\n | 'Simplify'\n | 'Summarize'\n | 'Tone'\n | 'Translate'\n\nexport type ActionPromptOptions = {\n layout?: string\n locale?: string\n prompt?: string\n systemPrompt?: string\n}\n\nexport type ActionPrompt = {\n layout?: (options?: ActionPromptOptions) => string\n name: ActionMenuItems\n system: (options: ActionPromptOptions) => string\n}\n\nexport type SeedPromptOptions = {\n fieldLabel: string\n fieldSchemaPaths: Record<string, any>\n fieldType: string\n path: string\n}\n\nexport type SeedPromptData = Omit<\n TypedCollection[typeof PLUGIN_INSTRUCTIONS_TABLE],\n 'createdAt' | 'id' | 'updatedAt'\n>\n\nexport type SeedPromptResult =\n | {\n data?: SeedPromptData\n }\n | {\n data?: SeedPromptData\n prompt: string\n system: string\n }\n | false\n | undefined\n | void\n\nexport type SeedPromptFunction = (\n options: SeedPromptOptions,\n) => Promise<SeedPromptResult> | SeedPromptResult\n\nexport type ActionMenuEvents =\n | 'onCompose'\n | 'onExpand'\n | 'onProofread'\n | 'onRephrase'\n | 'onSettings'\n | 'onSimplify'\n | 'onSummarize'\n | 'onTone'\n | 'onTranslate'\n\nexport type UseMenuEvents = {\n [key in ActionMenuEvents]?: (data?: unknown) => void\n}\n\nexport type UseMenuOptions = {\n isConfigAllowed: boolean\n}\n\nexport type BaseItemProps<T = any> = {\n children?: React.ReactNode\n disabled?: boolean\n hideIcon?: boolean\n isActive?: boolean\n isMenu?: boolean\n onClick: (data?: unknown) => void\n onMouseEnter?: MouseEventHandler<T> | undefined\n onMouseLeave?: MouseEventHandler<T> | undefined\n style?: CSSProperties | undefined\n title?: string\n}\n\nexport type ImageReference = {\n data: Blob\n name: string\n size: number\n type: string\n url: string\n}\n\nexport type GenerateImageParams = {\n images?: ImageReference[]\n size?: ImageGenerateParams['size']\n style?: ImageGenerateParams['style']\n version?: ImageGenerateParams['model']\n}\n\nexport type SerializedPromptField = {\n collections?: CollectionSlug[]\n name: string\n}\n\nexport type PromptFieldGetterContext = {\n collection: CollectionSlug\n type: string\n}\n\nexport type PromptField = {\n // If not provided, the value will be returned from the data object as-is\n getter?: (data: object, ctx: PromptFieldGetterContext) => Promise<string> | string\n} & SerializedPromptField\n"],"names":[],"mappings":"AAyOA,WAGyB"}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { JSONSchema } from 'openai/lib/jsonschema'\nimport type { ImageGenerateParams } from 'openai/resources/images'\nimport type {\n CollectionConfig,\n CollectionSlug,\n DataFromCollectionSlug,\n Endpoint,\n Field,\n File,\n GlobalConfig,\n GroupField,\n PayloadRequest,\n TypedCollection,\n} from 'payload'\nimport type { CSSProperties, MouseEventHandler } from 'react'\n\nimport type { MediaResult } from './ai/core/index.js'\nimport type {PLUGIN_INSTRUCTIONS_TABLE} from \"./defaults.js\";\n\nexport interface PluginConfigAccess {\n /**\n * Control access to AI generation features (generate text, images, audio)\n * @default () => !!req.user (requires authentication)\n */\n generate?: ({ req }: { req: PayloadRequest }) => boolean | Promise<boolean>\n /**\n * Control access to AI settings/configuration\n * @default () => !!req.user (requires authentication)\n */\n settings?: ({ req }: { req: PayloadRequest }) => boolean | Promise<boolean>\n}\n\nexport interface PluginOptions {\n /**\n * Provide local tags to filter language options from the Translate Menu\n * Check for the available local tags,\n * visit: https://www.npmjs.com/package/locale-codes\n * Example: [\"en-US\", \"zh-SG\", \"zh-CN\", \"en\"]\n */\n enabledLanguages?: string[]\n}\n\nexport type PluginConfigMediaUploadFunction = (\n result: MediaResult,\n {\n collection,\n request,\n }: {\n collection: CollectionSlug\n request: PayloadRequest\n },\n) => Promise<DataFromCollectionSlug<CollectionSlug>>\n\nexport interface PluginConfig {\n /**\n * Access control configuration for AI features\n * By default, all AI features require authentication\n */\n access?: PluginConfigAccess\n debugging?: boolean\n disableSponsorMessage?: boolean\n editorConfig?: { nodes: JSONSchema[] }\n fields?: Field[]\n generatePromptOnInit?: boolean\n generationModels?: ((defaultModels: GenerationModel[]) => GenerationModel[]) | GenerationModel[]\n globals?: {\n [key: GlobalConfig['slug']]: boolean\n }\n interfaceName?: string\n mediaUpload?: PluginConfigMediaUploadFunction\n options?: PluginOptions\n // Override the instructions collection config\n overrideInstructions?: Partial<CollectionConfig>\n promptFields?: PromptField[]\n /**\n * Custom action prompts for AI text generation\n * If not provided, uses default prompts\n * You can access default prompts by importing { defaultPrompts } from '@ai-stack/payloadcms'\n */\n prompts?: ActionPrompt[]\n /**\n * Custom seed prompt function for generating field-specific prompts\n * If not provided, uses default seed prompt function\n * You can access default seed prompts by importing { defaultSeedPrompts } from '@ai-stack/payloadcms'\n */\n seedPrompts?: SeedPromptFunction\n uploadCollectionSlug?: CollectionSlug\n}\n\nexport interface GenerationModel {\n fields: string[]\n generateText?: (prompt: string, system: string) => Promise<string>\n handler?: (prompt: string, options: any) => File | Promise<any> | Response\n id: string\n name: string\n output: 'audio' | 'file' | 'image' | 'json' | 'text' | 'video'\n settings?: GroupField\n supportsPromptOptimization?: boolean\n}\n\nexport interface GenerationConfig {\n models: GenerationModel[]\n provider: string\n}\n\nexport type GenerateTextarea<T = any> = (args: {\n collectionSlug: CollectionSlug\n doc: T\n documentId?: number | string\n locale?: string\n options?: any\n}) => Promise<string> | string\n\nexport interface Endpoints {\n fetchVoices?: Omit<Endpoint, 'root'>\n textarea: Omit<Endpoint, 'root'>\n upload: Omit<Endpoint, 'root'>\n videogenWebhook?: Omit<Endpoint, 'root'>\n}\n\nexport type ActionMenuItems =\n | 'Compose'\n | 'Expand'\n | 'Proofread'\n | 'Rephrase'\n | 'Settings'\n | 'Simplify'\n | 'Summarize'\n | 'Tone'\n | 'Translate'\n\nexport type ActionPromptOptions = {\n layout?: string\n locale?: string\n prompt?: string\n systemPrompt?: string\n}\n\nexport type ActionPrompt = {\n layout?: (options?: ActionPromptOptions) => string\n name: ActionMenuItems\n system: (options: ActionPromptOptions) => string\n}\n\nexport type SeedPromptOptions = {\n fieldLabel: string\n fieldSchemaPaths: Record<string, any>\n fieldType: string\n path: string\n}\n\nexport type SeedPromptData = Omit<\n TypedCollection[typeof PLUGIN_INSTRUCTIONS_TABLE],\n 'createdAt' | 'id' | 'updatedAt'\n>\n\nexport type SeedPromptResult =\n | {\n data?: SeedPromptData\n }\n | {\n data?: SeedPromptData\n prompt: string\n system: string\n }\n | false\n | undefined\n | void\n\nexport type SeedPromptFunction = (\n options: SeedPromptOptions,\n) => Promise<SeedPromptResult> | SeedPromptResult\n\nexport type ActionMenuEvents =\n | 'onCompose'\n | 'onExpand'\n | 'onProofread'\n | 'onRephrase'\n | 'onSettings'\n | 'onSimplify'\n | 'onSummarize'\n | 'onTone'\n | 'onTranslate'\n\nexport type UseMenuEvents = {\n [key in ActionMenuEvents]?: (data?: unknown) => void\n}\n\nexport type UseMenuOptions = {\n isConfigAllowed: boolean\n}\n\nexport type BaseItemProps<T = any> = {\n children?: React.ReactNode\n disabled?: boolean\n hideIcon?: boolean\n isActive?: boolean\n isMenu?: boolean\n onClick: (data?: unknown) => void\n onMouseEnter?: MouseEventHandler<T> | undefined\n onMouseLeave?: MouseEventHandler<T> | undefined\n style?: CSSProperties | undefined\n title?: string\n}\n\nexport type ImageReference = {\n data: Blob\n name: string\n size: number\n type: string\n url: string\n}\n\nexport type GenerateImageParams = {\n images?: ImageReference[]\n size?: ImageGenerateParams['size']\n style?: ImageGenerateParams['style']\n version?: ImageGenerateParams['model']\n}\n\nexport type SerializedPromptField = {\n collections?: CollectionSlug[]\n name: string\n}\n\nexport type PromptFieldGetterContext = {\n collection: CollectionSlug\n type: string\n}\n\nexport type PromptField = {\n // If not provided, the value will be returned from the data object as-is\n getter?: (data: object, ctx: PromptFieldGetterContext) => Promise<string> | string\n} & SerializedPromptField\n"],"names":[],"mappings":"AAsOA,WAGyB"}
@@ -1,43 +1,219 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import React from 'react';
3
+ import { Button, toast, useConfig } from '@payloadcms/ui';
4
+ // @ts-expect-error - Next.js types are not resolving correctly with nodenext but runtime is fine
5
+ import { useRouter } from 'next/navigation';
6
+ import React, { useContext, useEffect, useState } from 'react';
7
+ import { excludeCollections } from '../../defaults.js';
8
+ import { InstructionsContext } from '../../providers/InstructionsProvider/context.js';
4
9
  export const AIConfigDashboard = ()=>{
10
+ const { config: { collections, routes: { admin: adminRoute, api: apiRoute } } } = useConfig();
11
+ const router = useRouter();
12
+ const { refresh, setEnabledCollections: setEnabledCollectionsInContext } = useContext(InstructionsContext);
13
+ const [enabledCollections, setEnabledCollections] = useState([]);
14
+ const [isLoading, setIsLoading] = useState(true);
15
+ const [isSaving, setIsSaving] = useState(false);
16
+ const availableCollections = collections.filter((c)=>!excludeCollections.includes(c.slug) && !c.admin?.hidden);
17
+ useEffect(()=>{
18
+ const fetchSettings = async ()=>{
19
+ try {
20
+ const response = await fetch(`${apiRoute}/globals/ai-settings`);
21
+ if (response.ok) {
22
+ const data = await response.json();
23
+ // Handle both simple array and object wrapper if Payload wraps it
24
+ const storedEnabled = data.enabledCollections || [];
25
+ setEnabledCollections(Array.isArray(storedEnabled) ? storedEnabled : []);
26
+ }
27
+ } catch (error) {
28
+ console.error('Failed to fetch AI settings:', error);
29
+ } finally{
30
+ setIsLoading(false);
31
+ }
32
+ };
33
+ fetchSettings().catch((e)=>{
34
+ console.log(e);
35
+ });
36
+ }, [
37
+ apiRoute
38
+ ]);
39
+ const handleToggle = (slug)=>{
40
+ setEnabledCollections((prev)=>{
41
+ if (prev.includes(slug)) {
42
+ return prev.filter((s)=>s !== slug);
43
+ }
44
+ return [
45
+ ...prev,
46
+ slug
47
+ ];
48
+ });
49
+ };
50
+ const handleSave = async ()=>{
51
+ setIsSaving(true);
52
+ try {
53
+ // First fetch current settings to get ID or just rely on global update behavior
54
+ // We need to adhere to Payload's global update API
55
+ const response = await fetch(`${apiRoute}/globals/ai-settings`, {
56
+ body: JSON.stringify({
57
+ enabledCollections
58
+ }),
59
+ headers: {
60
+ 'Content-Type': 'application/json'
61
+ },
62
+ method: 'POST'
63
+ });
64
+ if (response.ok) {
65
+ toast.success('Settings saved successfully');
66
+ if (setEnabledCollectionsInContext) {
67
+ setEnabledCollectionsInContext(enabledCollections);
68
+ }
69
+ if (refresh) {
70
+ await refresh();
71
+ }
72
+ router.refresh();
73
+ } else {
74
+ toast.error('Failed to save settings');
75
+ }
76
+ } catch (error) {
77
+ console.error('Error saving settings:', error);
78
+ toast.error('Error saving settings');
79
+ } finally{
80
+ setIsSaving(false);
81
+ }
82
+ };
83
+ if (isLoading) {
84
+ return /*#__PURE__*/ _jsx("div", {
85
+ style: {
86
+ padding: '20px',
87
+ textAlign: 'center'
88
+ },
89
+ children: "Loading AI configuration..."
90
+ });
91
+ }
5
92
  return /*#__PURE__*/ _jsxs("div", {
6
93
  style: {
7
- alignItems: 'center',
8
94
  background: 'var(--theme-elevation-50)',
9
- border: '1px solid var(--theme-elevation-150)',
10
- borderRadius: '8px',
11
- display: 'flex',
12
- justifyContent: 'space-between',
95
+ // border: '1px solid var(--theme-elevation-150)',
96
+ // borderRadius: '8px',
13
97
  marginBottom: '20px',
14
- padding: '20px'
98
+ overflow: 'hidden'
15
99
  },
16
100
  children: [
17
101
  /*#__PURE__*/ _jsxs("div", {
102
+ style: {
103
+ alignItems: 'center',
104
+ borderBottom: '1px solid var(--theme-elevation-150)',
105
+ display: 'flex',
106
+ justifyContent: 'space-between',
107
+ padding: '20px'
108
+ },
18
109
  children: [
19
- /*#__PURE__*/ _jsx("h4", {
110
+ /*#__PURE__*/ _jsxs("div", {
111
+ children: [
112
+ /*#__PURE__*/ _jsx("h4", {
113
+ style: {
114
+ margin: '0 0 5px 0'
115
+ },
116
+ children: "AI Configuration"
117
+ }),
118
+ /*#__PURE__*/ _jsx("p", {
119
+ style: {
120
+ color: 'var(--theme-elevation-500)',
121
+ fontSize: '14px',
122
+ margin: '0'
123
+ },
124
+ children: "Manage your AI providers, API keys, and enable AI for specific collections."
125
+ })
126
+ ]
127
+ }),
128
+ /*#__PURE__*/ _jsxs("div", {
20
129
  style: {
21
- margin: '0 0 5px 0'
130
+ display: 'flex',
131
+ gap: '10px'
22
132
  },
23
- children: "AI Configuration"
133
+ children: [
134
+ /*#__PURE__*/ _jsx(Button, {
135
+ buttonStyle: "secondary",
136
+ el: "link",
137
+ to: `${adminRoute}/globals/ai-settings`,
138
+ children: "Settings"
139
+ }),
140
+ /*#__PURE__*/ _jsx(Button, {
141
+ disabled: isSaving,
142
+ onClick: handleSave,
143
+ children: isSaving ? 'Saving...' : 'Save Changes'
144
+ })
145
+ ]
146
+ })
147
+ ]
148
+ }),
149
+ /*#__PURE__*/ _jsxs("div", {
150
+ style: {
151
+ padding: '20px'
152
+ },
153
+ children: [
154
+ /*#__PURE__*/ _jsx("h5", {
155
+ style: {
156
+ marginBottom: '15px'
157
+ },
158
+ children: "Enabled Collections"
24
159
  }),
25
- /*#__PURE__*/ _jsx("p", {
160
+ /*#__PURE__*/ _jsx("div", {
26
161
  style: {
27
- color: 'var(--theme-elevation-500)',
28
- fontSize: '14px',
29
- margin: '0'
162
+ display: 'grid',
163
+ gap: '15px',
164
+ gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))'
30
165
  },
31
- children: "Manage your AI providers, API keys, and default models."
166
+ children: availableCollections.map((collection)=>{
167
+ const isEnabled = enabledCollections.includes(collection.slug);
168
+ return /*#__PURE__*/ _jsxs("button", {
169
+ onClick: ()=>handleToggle(collection.slug),
170
+ style: {
171
+ alignItems: 'center',
172
+ background: isEnabled ? 'var(--theme-elevation-100)' : 'var(--theme-elevation-50)',
173
+ border: `1px solid ${isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)'}`,
174
+ borderRadius: '6px',
175
+ cursor: 'pointer',
176
+ display: 'flex',
177
+ gap: '10px',
178
+ padding: '10px 15px',
179
+ textAlign: 'left',
180
+ transition: 'all 0.2s ease',
181
+ width: '100%'
182
+ },
183
+ type: "button",
184
+ children: [
185
+ /*#__PURE__*/ _jsx("div", {
186
+ style: {
187
+ alignItems: 'center',
188
+ background: isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)',
189
+ borderRadius: '12px',
190
+ display: 'flex',
191
+ height: '24px',
192
+ justifyContent: isEnabled ? 'flex-end' : 'flex-start',
193
+ padding: '2px',
194
+ transition: 'all 0.2s ease',
195
+ width: '44px'
196
+ },
197
+ children: /*#__PURE__*/ _jsx("div", {
198
+ style: {
199
+ background: 'white',
200
+ borderRadius: '50%',
201
+ height: '20px',
202
+ width: '20px'
203
+ }
204
+ })
205
+ }),
206
+ /*#__PURE__*/ _jsx("span", {
207
+ style: {
208
+ fontWeight: 500
209
+ },
210
+ children: typeof collection.labels?.singular === 'string' ? collection.labels.singular : collection.labels?.singular?.en || collection.slug
211
+ })
212
+ ]
213
+ }, collection.slug);
214
+ })
32
215
  })
33
216
  ]
34
- }),
35
- /*#__PURE__*/ _jsx("a", {
36
- href: "/admin/globals/ai-settings",
37
- children: /*#__PURE__*/ _jsx("button", {
38
- className: "btn btn--style-primary btn--size-small",
39
- children: "Manage AI Settings"
40
- })
41
217
  })
42
218
  ]
43
219
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/AIConfigDashboard/index.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\n\nexport const AIConfigDashboard: React.FC = () => {\n return (\n <div\n style={{\n alignItems: 'center',\n background: 'var(--theme-elevation-50)',\n border: '1px solid var(--theme-elevation-150)',\n borderRadius: '8px',\n display: 'flex',\n justifyContent: 'space-between',\n marginBottom: '20px',\n padding: '20px',\n }}\n >\n <div>\n <h4 style={{ margin: '0 0 5px 0' }}>AI Configuration</h4>\n <p style={{ color: 'var(--theme-elevation-500)', fontSize: '14px', margin: '0' }}>\n Manage your AI providers, API keys, and default models.\n </p>\n </div>\n <a href=\"/admin/globals/ai-settings\">\n <button className=\"btn btn--style-primary btn--size-small\">Manage AI Settings</button>\n </a>\n </div>\n )\n}\n"],"names":["React","AIConfigDashboard","div","style","alignItems","background","border","borderRadius","display","justifyContent","marginBottom","padding","h4","margin","p","color","fontSize","a","href","button","className"],"mappings":"AAAA;;AAEA,OAAOA,WAAW,QAAO;AAEzB,OAAO,MAAMC,oBAA8B;IACzC,qBACE,MAACC;QACCC,OAAO;YACLC,YAAY;YACZC,YAAY;YACZC,QAAQ;YACRC,cAAc;YACdC,SAAS;YACTC,gBAAgB;YAChBC,cAAc;YACdC,SAAS;QACX;;0BAEA,MAACT;;kCACC,KAACU;wBAAGT,OAAO;4BAAEU,QAAQ;wBAAY;kCAAG;;kCACpC,KAACC;wBAAEX,OAAO;4BAAEY,OAAO;4BAA8BC,UAAU;4BAAQH,QAAQ;wBAAI;kCAAG;;;;0BAIpF,KAACI;gBAAEC,MAAK;0BACN,cAAA,KAACC;oBAAOC,WAAU;8BAAyC;;;;;AAInE,EAAC"}
1
+ {"version":3,"sources":["../../../src/ui/AIConfigDashboard/index.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useConfig } from '@payloadcms/ui'\n// @ts-expect-error - Next.js types are not resolving correctly with nodenext but runtime is fine\nimport { useRouter } from 'next/navigation'\nimport React, { useContext, useEffect, useState } from 'react'\n\nimport { excludeCollections } from '../../defaults.js'\nimport { InstructionsContext } from '../../providers/InstructionsProvider/context.js'\n\nexport const AIConfigDashboard: React.FC = () => {\n const {\n config: {\n collections,\n routes: { admin: adminRoute, api: apiRoute },\n },\n } = useConfig()\n const router = useRouter()\n const { refresh, setEnabledCollections: setEnabledCollectionsInContext } =\n useContext(InstructionsContext)\n\n const [enabledCollections, setEnabledCollections] = useState<string[]>([])\n const [isLoading, setIsLoading] = useState(true)\n const [isSaving, setIsSaving] = useState(false)\n\n const availableCollections = collections.filter(\n (c) =>\n !excludeCollections.includes(c.slug) &&\n !(c.admin as unknown as { hidden?: ((args: any) => boolean) | boolean })?.hidden,\n )\n\n useEffect(() => {\n const fetchSettings = async () => {\n try {\n const response = await fetch(`${apiRoute}/globals/ai-settings`)\n if (response.ok) {\n const data = await response.json()\n // Handle both simple array and object wrapper if Payload wraps it\n const storedEnabled = data.enabledCollections || []\n setEnabledCollections(Array.isArray(storedEnabled) ? storedEnabled : [])\n }\n } catch (error) {\n console.error('Failed to fetch AI settings:', error)\n } finally {\n setIsLoading(false)\n }\n }\n\n fetchSettings().catch((e) => {\n console.log(e)\n })\n }, [apiRoute])\n\n const handleToggle = (slug: string) => {\n setEnabledCollections((prev) => {\n if (prev.includes(slug)) {\n return prev.filter((s) => s !== slug)\n }\n return [...prev, slug]\n })\n }\n\n const handleSave = async () => {\n setIsSaving(true)\n try {\n // First fetch current settings to get ID or just rely on global update behavior\n // We need to adhere to Payload's global update API\n const response = await fetch(`${apiRoute}/globals/ai-settings`, {\n body: JSON.stringify({\n enabledCollections,\n }),\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (response.ok) {\n toast.success('Settings saved successfully')\n if (setEnabledCollectionsInContext) {\n setEnabledCollectionsInContext(enabledCollections)\n }\n if (refresh) {\n await refresh()\n }\n router.refresh()\n } else {\n toast.error('Failed to save settings')\n }\n } catch (error) {\n console.error('Error saving settings:', error)\n toast.error('Error saving settings')\n } finally {\n setIsSaving(false)\n }\n }\n\n if (isLoading) {\n return <div style={{ padding: '20px', textAlign: 'center' }}>Loading AI configuration...</div>\n }\n\n return (\n <div\n style={{\n background: 'var(--theme-elevation-50)',\n // border: '1px solid var(--theme-elevation-150)',\n // borderRadius: '8px',\n marginBottom: '20px',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n alignItems: 'center',\n borderBottom: '1px solid var(--theme-elevation-150)',\n display: 'flex',\n justifyContent: 'space-between',\n padding: '20px',\n }}\n >\n <div>\n <h4 style={{ margin: '0 0 5px 0' }}>AI Configuration</h4>\n <p style={{ color: 'var(--theme-elevation-500)', fontSize: '14px', margin: '0' }}>\n Manage your AI providers, API keys, and enable AI for specific collections.\n </p>\n </div>\n <div style={{ display: 'flex', gap: '10px' }}>\n <Button buttonStyle=\"secondary\" el=\"link\" to={`${adminRoute}/globals/ai-settings`}>\n Settings\n </Button>\n <Button\n disabled={isSaving}\n onClick={handleSave}\n >\n {isSaving ? 'Saving...' : 'Save Changes'}\n </Button>\n </div>\n </div>\n\n <div style={{ padding: '20px' }}>\n <h5 style={{ marginBottom: '15px' }}>Enabled Collections</h5>\n <div\n style={{\n display: 'grid',\n gap: '15px',\n gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',\n }}\n >\n {availableCollections.map((collection) => {\n const isEnabled = enabledCollections.includes(collection.slug)\n return (\n <button\n key={collection.slug}\n onClick={() => handleToggle(collection.slug)}\n style={{\n alignItems: 'center',\n background: isEnabled\n ? 'var(--theme-elevation-100)'\n : 'var(--theme-elevation-50)',\n border: `1px solid ${isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)'}`,\n borderRadius: '6px',\n cursor: 'pointer',\n display: 'flex',\n gap: '10px',\n padding: '10px 15px',\n textAlign: 'left',\n transition: 'all 0.2s ease',\n width: '100%',\n }}\n type=\"button\"\n >\n <div\n style={{\n alignItems: 'center',\n background: isEnabled\n ? 'var(--theme-text-success)'\n : 'var(--theme-elevation-200)',\n borderRadius: '12px',\n display: 'flex',\n height: '24px',\n justifyContent: isEnabled ? 'flex-end' : 'flex-start',\n padding: '2px',\n transition: 'all 0.2s ease',\n width: '44px',\n }}\n >\n <div\n style={{\n background: 'white',\n borderRadius: '50%',\n height: '20px',\n width: '20px',\n }}\n />\n </div>\n <span style={{ fontWeight: 500 }}>\n {typeof collection.labels?.singular === 'string'\n ? collection.labels.singular\n : collection.labels?.singular?.en || collection.slug}\n </span>\n </button>\n )\n })}\n </div>\n </div>\n </div>\n )\n}\n"],"names":["Button","toast","useConfig","useRouter","React","useContext","useEffect","useState","excludeCollections","InstructionsContext","AIConfigDashboard","config","collections","routes","admin","adminRoute","api","apiRoute","router","refresh","setEnabledCollections","setEnabledCollectionsInContext","enabledCollections","isLoading","setIsLoading","isSaving","setIsSaving","availableCollections","filter","c","includes","slug","hidden","fetchSettings","response","fetch","ok","data","json","storedEnabled","Array","isArray","error","console","catch","e","log","handleToggle","prev","s","handleSave","body","JSON","stringify","headers","method","success","div","style","padding","textAlign","background","marginBottom","overflow","alignItems","borderBottom","display","justifyContent","h4","margin","p","color","fontSize","gap","buttonStyle","el","to","disabled","onClick","h5","gridTemplateColumns","map","collection","isEnabled","button","border","borderRadius","cursor","transition","width","type","height","span","fontWeight","labels","singular","en"],"mappings":"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"}
@@ -1,24 +1,170 @@
1
1
  'use client';
2
- import React from 'react';
2
+ import { Button, toast, useConfig } from '@payloadcms/ui';
3
+ // @ts-expect-error - Next.js types are not resolving correctly with nodenext but runtime is fine
4
+ import { useRouter } from 'next/navigation';
5
+ import React, { useContext, useEffect, useState } from 'react';
6
+ import { excludeCollections } from '../../defaults.js';
7
+ import { InstructionsContext } from '../../providers/InstructionsProvider/context.js';
3
8
  export const AIConfigDashboard = () => {
9
+ const { config: { collections, routes: { admin: adminRoute, api: apiRoute }, }, } = useConfig();
10
+ const router = useRouter();
11
+ const { refresh, setEnabledCollections: setEnabledCollectionsInContext } = useContext(InstructionsContext);
12
+ const [enabledCollections, setEnabledCollections] = useState([]);
13
+ const [isLoading, setIsLoading] = useState(true);
14
+ const [isSaving, setIsSaving] = useState(false);
15
+ const availableCollections = collections.filter((c) => !excludeCollections.includes(c.slug) &&
16
+ !c.admin?.hidden);
17
+ useEffect(() => {
18
+ const fetchSettings = async () => {
19
+ try {
20
+ const response = await fetch(`${apiRoute}/globals/ai-settings`);
21
+ if (response.ok) {
22
+ const data = await response.json();
23
+ // Handle both simple array and object wrapper if Payload wraps it
24
+ const storedEnabled = data.enabledCollections || [];
25
+ setEnabledCollections(Array.isArray(storedEnabled) ? storedEnabled : []);
26
+ }
27
+ }
28
+ catch (error) {
29
+ console.error('Failed to fetch AI settings:', error);
30
+ }
31
+ finally {
32
+ setIsLoading(false);
33
+ }
34
+ };
35
+ fetchSettings().catch((e) => {
36
+ console.log(e);
37
+ });
38
+ }, [apiRoute]);
39
+ const handleToggle = (slug) => {
40
+ setEnabledCollections((prev) => {
41
+ if (prev.includes(slug)) {
42
+ return prev.filter((s) => s !== slug);
43
+ }
44
+ return [...prev, slug];
45
+ });
46
+ };
47
+ const handleSave = async () => {
48
+ setIsSaving(true);
49
+ try {
50
+ // First fetch current settings to get ID or just rely on global update behavior
51
+ // We need to adhere to Payload's global update API
52
+ const response = await fetch(`${apiRoute}/globals/ai-settings`, {
53
+ body: JSON.stringify({
54
+ enabledCollections,
55
+ }),
56
+ headers: {
57
+ 'Content-Type': 'application/json',
58
+ },
59
+ method: 'POST',
60
+ });
61
+ if (response.ok) {
62
+ toast.success('Settings saved successfully');
63
+ if (setEnabledCollectionsInContext) {
64
+ setEnabledCollectionsInContext(enabledCollections);
65
+ }
66
+ if (refresh) {
67
+ await refresh();
68
+ }
69
+ router.refresh();
70
+ }
71
+ else {
72
+ toast.error('Failed to save settings');
73
+ }
74
+ }
75
+ catch (error) {
76
+ console.error('Error saving settings:', error);
77
+ toast.error('Error saving settings');
78
+ }
79
+ finally {
80
+ setIsSaving(false);
81
+ }
82
+ };
83
+ if (isLoading) {
84
+ return <div style={{ padding: '20px', textAlign: 'center' }}>Loading AI configuration...</div>;
85
+ }
4
86
  return (<div style={{
5
- alignItems: 'center',
6
87
  background: 'var(--theme-elevation-50)',
7
- border: '1px solid var(--theme-elevation-150)',
8
- borderRadius: '8px',
88
+ // border: '1px solid var(--theme-elevation-150)',
89
+ // borderRadius: '8px',
90
+ marginBottom: '20px',
91
+ overflow: 'hidden',
92
+ }}>
93
+ <div style={{
94
+ alignItems: 'center',
95
+ borderBottom: '1px solid var(--theme-elevation-150)',
9
96
  display: 'flex',
10
97
  justifyContent: 'space-between',
11
- marginBottom: '20px',
12
98
  padding: '20px',
13
99
  }}>
14
- <div>
15
- <h4 style={{ margin: '0 0 5px 0' }}>AI Configuration</h4>
16
- <p style={{ color: 'var(--theme-elevation-500)', fontSize: '14px', margin: '0' }}>
17
- Manage your AI providers, API keys, and default models.
18
- </p>
100
+ <div>
101
+ <h4 style={{ margin: '0 0 5px 0' }}>AI Configuration</h4>
102
+ <p style={{ color: 'var(--theme-elevation-500)', fontSize: '14px', margin: '0' }}>
103
+ Manage your AI providers, API keys, and enable AI for specific collections.
104
+ </p>
105
+ </div>
106
+ <div style={{ display: 'flex', gap: '10px' }}>
107
+ <Button buttonStyle="secondary" el="link" to={`${adminRoute}/globals/ai-settings`}>
108
+ Settings
109
+ </Button>
110
+ <Button disabled={isSaving} onClick={handleSave}>
111
+ {isSaving ? 'Saving...' : 'Save Changes'}
112
+ </Button>
113
+ </div>
114
+ </div>
115
+
116
+ <div style={{ padding: '20px' }}>
117
+ <h5 style={{ marginBottom: '15px' }}>Enabled Collections</h5>
118
+ <div style={{
119
+ display: 'grid',
120
+ gap: '15px',
121
+ gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
122
+ }}>
123
+ {availableCollections.map((collection) => {
124
+ const isEnabled = enabledCollections.includes(collection.slug);
125
+ return (<button key={collection.slug} onClick={() => handleToggle(collection.slug)} style={{
126
+ alignItems: 'center',
127
+ background: isEnabled
128
+ ? 'var(--theme-elevation-100)'
129
+ : 'var(--theme-elevation-50)',
130
+ border: `1px solid ${isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)'}`,
131
+ borderRadius: '6px',
132
+ cursor: 'pointer',
133
+ display: 'flex',
134
+ gap: '10px',
135
+ padding: '10px 15px',
136
+ textAlign: 'left',
137
+ transition: 'all 0.2s ease',
138
+ width: '100%',
139
+ }} type="button">
140
+ <div style={{
141
+ alignItems: 'center',
142
+ background: isEnabled
143
+ ? 'var(--theme-text-success)'
144
+ : 'var(--theme-elevation-200)',
145
+ borderRadius: '12px',
146
+ display: 'flex',
147
+ height: '24px',
148
+ justifyContent: isEnabled ? 'flex-end' : 'flex-start',
149
+ padding: '2px',
150
+ transition: 'all 0.2s ease',
151
+ width: '44px',
152
+ }}>
153
+ <div style={{
154
+ background: 'white',
155
+ borderRadius: '50%',
156
+ height: '20px',
157
+ width: '20px',
158
+ }}/>
159
+ </div>
160
+ <span style={{ fontWeight: 500 }}>
161
+ {typeof collection.labels?.singular === 'string'
162
+ ? collection.labels.singular
163
+ : collection.labels?.singular?.en || collection.slug}
164
+ </span>
165
+ </button>);
166
+ })}
167
+ </div>
19
168
  </div>
20
- <a href="/admin/globals/ai-settings">
21
- <button className="btn btn--style-primary btn--size-small">Manage AI Settings</button>
22
- </a>
23
169
  </div>);
24
170
  };
@@ -135,10 +135,12 @@ export const Compose = ({ descriptionProps, forceVisible, instructionId, isConfi
135
135
  });
136
136
  const setIfValueIsLexicalState = useCallback((val)=>{
137
137
  if (val && typeof val === 'object' && 'root' in val && lexicalEditor) {
138
- setSafeLexicalState(JSON.stringify(val), lexicalEditor);
138
+ // Pass the object directly to our safe setter which handles validation
139
+ setSafeLexicalState(val, lexicalEditor);
139
140
  }
140
- // DO NOT PROVIDE lexicalEditor as a dependency, it freaks out and does not update the editor after first undo/redo - revisit
141
- }, []);
141
+ }, [
142
+ lexicalEditor
143
+ ]);
142
144
  const popupRender = useCallback(({ close })=>{
143
145
  return /*#__PURE__*/ _jsx(Menu, {
144
146
  isLoading: isProcessing || isLoading,