@mvriu5/payload-ai 1.1.1 → 1.3.2

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 (55) hide show
  1. package/README.md +42 -0
  2. package/dist/components/Icons.d.ts +0 -8
  3. package/dist/components/Icons.js +4 -216
  4. package/dist/components/{ActionToast.d.ts → action-toast/ActionToast.d.ts} +2 -2
  5. package/dist/components/{ActionToast.js → action-toast/ActionToast.js} +27 -35
  6. package/dist/components/{ActionToast.module.css → action-toast/ActionToast.module.css} +0 -79
  7. package/dist/components/ai-input/AIInput.js +433 -0
  8. package/dist/components/{AIInput.module.css → ai-input/AIInput.module.css} +119 -42
  9. package/dist/components/ai-input/badge.d.ts +14 -0
  10. package/dist/components/ai-input/badge.js +85 -0
  11. package/dist/components/{AuditLogList.d.ts → audit-log-list/AuditLogList.d.ts} +4 -8
  12. package/dist/components/{AuditLogList.js → audit-log-list/AuditLogList.js} +49 -21
  13. package/dist/components/{AuditLogList.module.css → audit-log-list/AuditLogList.module.css} +11 -65
  14. package/dist/components/dashboard/Dashboard.d.ts +2 -0
  15. package/dist/components/dashboard/Dashboard.js +15 -0
  16. package/dist/components/dashboard/Dashboard.module.css +13 -0
  17. package/dist/components/{DiffDialog.d.ts → diff-dialog/DiffDialog.d.ts} +2 -2
  18. package/dist/components/{DiffDialog.js → diff-dialog/DiffDialog.js} +200 -189
  19. package/dist/components/{DiffDialog.module.css → diff-dialog/DiffDialog.module.css} +17 -35
  20. package/dist/components/hooks/useAIChatStream.d.ts +39 -0
  21. package/dist/components/hooks/useAIChatStream.js +217 -0
  22. package/dist/components/hooks/useAISettings.js +26 -25
  23. package/dist/components/hooks/useAuditLog.d.ts +11 -0
  24. package/dist/components/hooks/useAuditLog.js +41 -0
  25. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +1 -1
  26. package/dist/components/hooks/useDocumentMentionSuggestions.js +31 -27
  27. package/dist/components/hooks/useMentions.d.ts +58 -0
  28. package/dist/components/hooks/useMentions.js +276 -0
  29. package/dist/components/hooks/usePluginConfig.d.ts +52 -0
  30. package/dist/components/hooks/usePluginConfig.js +20 -0
  31. package/dist/components/{MentionPopover.d.ts → mention-popover/MentionPopover.d.ts} +2 -4
  32. package/dist/components/{MentionPopover.js → mention-popover/MentionPopover.js} +1 -8
  33. package/dist/components/{MentionPopover.module.css → mention-popover/MentionPopover.module.css} +1 -1
  34. package/dist/exports/client.d.ts +2 -2
  35. package/dist/exports/client.js +2 -2
  36. package/dist/handlers/applyActionHandler.js +341 -112
  37. package/dist/handlers/chatHandler.js +940 -84
  38. package/dist/handlers/mediaUploadHandler.d.ts +7 -0
  39. package/dist/handlers/mediaUploadHandler.js +99 -0
  40. package/dist/handlers/mentionSuggestionHandler.js +16 -14
  41. package/dist/handlers/proposalDiffHandler.js +117 -50
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.js +39 -7
  44. package/dist/payload/collectionPermissions.js +3 -1
  45. package/dist/payload/logging.d.ts +9 -0
  46. package/dist/payload/logging.js +14 -0
  47. package/dist/payload/normalizeData.d.ts +16 -12
  48. package/dist/payload/normalizeData.js +47 -14
  49. package/dist/payload/proposalData.d.ts +31 -0
  50. package/dist/payload/proposalData.js +578 -0
  51. package/dist/payload/schemaContext.d.ts +3 -7
  52. package/dist/payload/schemaContext.js +147 -66
  53. package/package.json +6 -6
  54. package/dist/components/AIInput.js +0 -747
  55. /package/dist/components/{AIInput.d.ts → ai-input/AIInput.d.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { getCollectionPermissions, getCollectionSlugsForAction } from "./collectionPermissions.js";
2
- import { getCollectionFields, isAuthCollection } from "./normalizeData.js";
2
+ import { getSchemaFields as getNormalizeSchemaFields, hasDrafts, isAuthCollection } from "./normalizeData.js";
3
3
  import { getSerializableLabel, isInternalCollection, isRecord } from "./shared.js";
4
4
  const getSerializableRelationTo = (relationTo)=>{
5
5
  if (typeof relationTo === "string") return relationTo;
@@ -44,34 +44,32 @@ const getLocaleConfigs = (req)=>{
44
44
  };
45
45
  const getSerializableOptions = (options)=>{
46
46
  if (!Array.isArray(options)) return undefined;
47
- const serializedOptions = options.map((option)=>{
47
+ const serializedOptions = options.flatMap((option)=>{
48
48
  if (typeof option === "string") {
49
- return {
50
- label: option,
51
- value: option
52
- };
49
+ return [
50
+ {
51
+ label: option,
52
+ value: option
53
+ }
54
+ ];
53
55
  }
54
- if (!isRecord(option)) return null;
56
+ if (!isRecord(option)) return [];
55
57
  const label = "label" in option ? getSerializableLabel(option.label) : undefined;
56
58
  const value = "value" in option && typeof option.value === "string" ? option.value : undefined;
57
- if (!label && !value) return null;
58
- return {
59
- ...label ? {
60
- label
61
- } : {},
62
- ...value ? {
63
- value
64
- } : {}
65
- };
66
- }).filter(Boolean);
59
+ if (!label && !value) return [];
60
+ return [
61
+ {
62
+ ...label ? {
63
+ label
64
+ } : {},
65
+ ...value ? {
66
+ value
67
+ } : {}
68
+ }
69
+ ];
70
+ });
67
71
  return serializedOptions.length > 0 ? serializedOptions : undefined;
68
72
  };
69
- const hasDrafts = (config)=>{
70
- const versions = config?.versions;
71
- if (!versions || versions === true) return false;
72
- if (!isRecord(versions)) return false;
73
- return Boolean(versions.drafts);
74
- };
75
73
  const toNormalizeCollectionConfig = (config)=>{
76
74
  if (!config) return undefined;
77
75
  return {
@@ -82,22 +80,7 @@ const toNormalizeCollectionConfig = (config)=>{
82
80
  };
83
81
  const getSchemaFields = (config)=>{
84
82
  const normalizedConfig = toNormalizeCollectionConfig(config);
85
- const fields = isAuthCollection(normalizedConfig) ? getCollectionFields(normalizedConfig) : [
86
- ...config?.fields || []
87
- ];
88
- if (hasDrafts(config)) {
89
- fields.push({
90
- label: "Status",
91
- name: "_status",
92
- options: [
93
- "draft",
94
- "published"
95
- ],
96
- required: true,
97
- type: "select"
98
- });
99
- }
100
- return fields;
83
+ return getNormalizeSchemaFields(normalizedConfig);
101
84
  };
102
85
  const getLocalizedFieldNames = (fields, path = "")=>{
103
86
  return fields.flatMap((field)=>{
@@ -191,6 +174,14 @@ const describeField = (field)=>{
191
174
  ...options ? {
192
175
  options
193
176
  } : {},
177
+ ...field.type === "blocks" && field.blocks ? {
178
+ acceptedBlockTypes: field.blocks.map((block)=>block.slug),
179
+ blockExample: [
180
+ {
181
+ ...getBlockExample(field.blocks[0])
182
+ }
183
+ ]
184
+ } : {},
194
185
  ...field.admin?.condition ? {
195
186
  hasCondition: true
196
187
  } : {},
@@ -208,6 +199,8 @@ const describeBlock = (block)=>{
208
199
  const cached = blockDescriptionCache.get(block);
209
200
  if (cached) return cached;
210
201
  const description = {
202
+ blockType: block.slug,
203
+ example: getBlockExample(block),
211
204
  fields: (block.fields || []).map(describeField),
212
205
  label: getSerializableLabel(block.labels?.singular) || block.slug,
213
206
  slug: block.slug
@@ -215,6 +208,43 @@ const describeBlock = (block)=>{
215
208
  blockDescriptionCache.set(block, description);
216
209
  return description;
217
210
  };
211
+ const getBlockExample = (block)=>{
212
+ if (!block) return {};
213
+ const example = {
214
+ blockType: block.slug
215
+ };
216
+ for (const field of block.fields || []){
217
+ if (!field.name) continue;
218
+ if (field.type === "array") {
219
+ example[field.name] = field.fields?.length ? [
220
+ Object.fromEntries(field.fields.filter((childField)=>Boolean(childField.name)).slice(0, 2).map((childField)=>[
221
+ childField.name,
222
+ childField.type === "checkbox" ? false : `<${childField.name}>`
223
+ ]))
224
+ ] : [];
225
+ continue;
226
+ }
227
+ if (field.type === "blocks") {
228
+ example[field.name] = field.blocks?.length ? [
229
+ getBlockExample(field.blocks[0])
230
+ ] : [];
231
+ continue;
232
+ }
233
+ if (field.type === "group") {
234
+ example[field.name] = Object.fromEntries((field.fields || []).filter((childField)=>Boolean(childField.name)).slice(0, 2).map((childField)=>[
235
+ childField.name,
236
+ childField.type === "checkbox" ? false : `<${childField.name}>`
237
+ ]));
238
+ continue;
239
+ }
240
+ if (field.type === "checkbox") {
241
+ example[field.name] = false;
242
+ continue;
243
+ }
244
+ example[field.name] = `<${field.name}>`;
245
+ }
246
+ return example;
247
+ };
218
248
  export const describeCollectionLikeConfig = ({ config, permissions, type })=>{
219
249
  return {
220
250
  ...describeCollectionLikeSummary({
@@ -262,8 +292,76 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, collec
262
292
  const context = [];
263
293
  const seen = new Set();
264
294
  const localeConfigs = getLocaleConfigs(req);
265
- const selectedLocales = (mentions.filter((mention)=>mention.type === "locale" && mention.slug).map((mention)=>mention.slug) || []).filter((locale, index, array)=>array.indexOf(locale) === index);
295
+ const localeConfigsByCode = new Map(localeConfigs.map((locale)=>[
296
+ locale.code,
297
+ locale
298
+ ]));
299
+ const collectionSlugSet = new Set(collectionSlugs);
300
+ const globalSlugSet = new Set(globalSlugs);
301
+ const collectionConfigsBySlug = new Map(req.payload.config.collections.map((collection)=>[
302
+ collection.slug,
303
+ collection
304
+ ]));
305
+ const globalConfigsBySlug = new Map(req.payload.config.globals?.map((global)=>[
306
+ global.slug,
307
+ global
308
+ ]));
309
+ const blockContextsBySlug = new Map();
310
+ for (const block of blockContexts){
311
+ const blocks = blockContextsBySlug.get(block.slug) || [];
312
+ blocks.push(block);
313
+ blockContextsBySlug.set(block.slug, blocks);
314
+ }
315
+ const selectedLocales = [];
316
+ const selectedLocaleSet = new Set();
317
+ for (const mention of mentions){
318
+ if (mention.type !== "locale" || !mention.slug || selectedLocaleSet.has(mention.slug)) continue;
319
+ selectedLocaleSet.add(mention.slug);
320
+ selectedLocales.push(mention.slug);
321
+ }
266
322
  const activeLocale = selectedLocales.at(-1);
323
+ const mentionDocResults = await Promise.all(mentions.slice(0, 8).map(async (mention)=>{
324
+ if (mention.type === "global" && mention.slug) {
325
+ const slug = mention.slug;
326
+ if (!globalSlugSet.has(slug)) return null;
327
+ const globalConfig = globalConfigsBySlug.get(slug);
328
+ if (!globalConfig) return null;
329
+ return {
330
+ doc: await req.payload.findGlobal({
331
+ depth: 2,
332
+ ...activeLocale ? {
333
+ locale: activeLocale
334
+ } : {},
335
+ overrideAccess: false,
336
+ req,
337
+ slug: slug
338
+ }).catch(()=>null),
339
+ key: `global:${slug}`
340
+ };
341
+ }
342
+ if (mention.type === "doc" && mention.collection && mention.id) {
343
+ const slug = mention.collection;
344
+ if (isInternalCollection(slug) || !collectionSlugSet.has(slug)) return null;
345
+ return {
346
+ doc: await req.payload.findByID({
347
+ collection: slug,
348
+ depth: 2,
349
+ id: mention.id,
350
+ ...activeLocale ? {
351
+ locale: activeLocale
352
+ } : {},
353
+ overrideAccess: false,
354
+ req
355
+ }).catch(()=>null),
356
+ key: `doc:${slug}:${mention.id}`
357
+ };
358
+ }
359
+ return null;
360
+ }));
361
+ const docsByMentionKey = new Map();
362
+ for (const result of mentionDocResults){
363
+ if (result) docsByMentionKey.set(result.key, result.doc);
364
+ }
267
365
  if (localeConfigs.length > 0) {
268
366
  context.push({
269
367
  activeLocale,
@@ -275,7 +373,7 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, collec
275
373
  }
276
374
  for (const mention of mentions.slice(0, 8)){
277
375
  if (mention.type === "locale" && mention.slug) {
278
- const locale = localeConfigs.find((item)=>item.code === mention.slug);
376
+ const locale = localeConfigsByCode.get(mention.slug);
279
377
  if (!locale) continue;
280
378
  const key = `locale:${locale.code}`;
281
379
  if (seen.has(key)) continue;
@@ -288,8 +386,8 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, collec
288
386
  if (mention.type === "collection" && mention.slug) {
289
387
  const slug = mention.slug;
290
388
  const key = `collection:${slug}`;
291
- if (seen.has(key) || isInternalCollection(slug) || !collectionSlugs.includes(slug)) continue;
292
- const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === slug);
389
+ if (seen.has(key) || isInternalCollection(slug) || !collectionSlugSet.has(slug)) continue;
390
+ const collectionConfig = collectionConfigsBySlug.get(slug);
293
391
  if (!collectionConfig) continue;
294
392
  seen.add(key);
295
393
  context.push(describeCollectionLikeConfig({
@@ -301,18 +399,10 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, collec
301
399
  if (mention.type === "global" && mention.slug) {
302
400
  const slug = mention.slug;
303
401
  const key = `global:${slug}`;
304
- if (seen.has(key) || !globalSlugs.includes(slug)) continue;
305
- const globalConfig = req.payload.config.globals?.find((global)=>global.slug === slug);
402
+ if (seen.has(key) || !globalSlugSet.has(slug)) continue;
403
+ const globalConfig = globalConfigsBySlug.get(slug);
306
404
  if (!globalConfig) continue;
307
- const globalDoc = await req.payload.findGlobal({
308
- depth: 2,
309
- ...activeLocale ? {
310
- locale: activeLocale
311
- } : {},
312
- overrideAccess: false,
313
- req,
314
- slug: slug
315
- }).catch(()=>null);
405
+ const globalDoc = docsByMentionKey.get(key) ?? null;
316
406
  seen.add(key);
317
407
  context.push({
318
408
  ...describeCollectionLikeConfig({
@@ -323,7 +413,7 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, collec
323
413
  });
324
414
  }
325
415
  if (mention.type === "block" && mention.slug) {
326
- const matchingBlocks = blockContexts.filter((block)=>block.slug === mention.slug && (!mention.parent || block.parent === mention.parent));
416
+ const matchingBlocks = (blockContextsBySlug.get(mention.slug) || []).filter((block)=>!mention.parent || block.parent === mention.parent);
327
417
  for (const block of matchingBlocks){
328
418
  const key = `block:${block.parent}:${block.slug}`;
329
419
  if (seen.has(key)) continue;
@@ -337,17 +427,8 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, collec
337
427
  if (mention.type === "doc" && mention.collection && mention.id) {
338
428
  const slug = mention.collection;
339
429
  const key = `doc:${slug}:${mention.id}`;
340
- if (seen.has(key) || isInternalCollection(slug) || !collectionSlugs.includes(slug)) continue;
341
- const doc = await req.payload.findByID({
342
- collection: slug,
343
- depth: 2,
344
- id: mention.id,
345
- ...activeLocale ? {
346
- locale: activeLocale
347
- } : {},
348
- overrideAccess: false,
349
- req
350
- }).catch(()=>null);
430
+ if (seen.has(key) || isInternalCollection(slug) || !collectionSlugSet.has(slug)) continue;
431
+ const doc = docsByMentionKey.get(key);
351
432
  if (!doc) continue;
352
433
  seen.add(key);
353
434
  context.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mvriu5/payload-ai",
3
- "version": "1.1.1",
3
+ "version": "1.3.2",
4
4
  "description": "AI assistant plugin for Payload CMS with provider selection, CMS mentions, and signed action proposals.",
5
5
  "keywords": [
6
6
  "payload",
@@ -63,7 +63,7 @@
63
63
  "test:e2e:headed": "playwright test --headed",
64
64
  "test:handlers": "vitest run tests/handlers",
65
65
  "test:unit": "vitest run tests/unit",
66
- "setup:hooks": "git config core.hooksPath .githooks"
66
+ "knip": "knip"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@ai-sdk/anthropic": "^3.0.81",
@@ -71,21 +71,21 @@
71
71
  "@ai-sdk/mistral": "^3.0.37",
72
72
  "@ai-sdk/openai": "^3.0.67",
73
73
  "@openrouter/ai-sdk-provider": "^2.9.1",
74
- "@playwright/test": "^1.56.1",
75
74
  "@payloadcms/db-postgres": "3.84.1",
76
75
  "@payloadcms/next": "3.84.1",
77
76
  "@payloadcms/richtext-lexical": "3.84.1",
78
77
  "@payloadcms/ui": "3.84.1",
78
+ "@playwright/test": "^1.56.1",
79
79
  "@swc/cli": "0.8.1",
80
80
  "@types/node": "25.9.3",
81
81
  "@types/react": "19.2.17",
82
82
  "@types/react-dom": "19.2.3",
83
83
  "copyfiles": "2.4.1",
84
- "cross-env": "^7.0.3",
84
+ "cross-env": "^10.1.0",
85
85
  "eslint": "^10.5.0",
86
- "graphql": "^16.8.1",
86
+ "graphql": "^17.0.1",
87
87
  "jsdom": "^29.1.1",
88
- "knip": "^6.16.1",
88
+ "knip": "^6.18.0",
89
89
  "next": "16.2.9",
90
90
  "payload": "3.84.1",
91
91
  "prettier": "^3.8.4",