@mvriu5/payload-ai 0.5.8 → 0.5.10

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 (65) hide show
  1. package/README.md +62 -6
  2. package/dist/ai/{proposals.d.ts → proposalSigning.d.ts} +1 -1
  3. package/dist/ai/{proposals.js → proposalSigning.js} +6 -11
  4. package/dist/ai/providerOptions.d.ts +17 -1
  5. package/dist/ai/providerOptions.js +35 -7
  6. package/dist/ai/providerRuntime.d.ts +2 -2
  7. package/dist/ai/providerRuntime.js +50 -16
  8. package/dist/ai/sensitiveData.js +2 -6
  9. package/dist/components/AIInput.d.ts +2 -1
  10. package/dist/components/AIInput.js +327 -41
  11. package/dist/components/AIInput.module.css +41 -6
  12. package/dist/components/APIKeyField.d.ts +3 -0
  13. package/dist/components/{AIApiKeyField.js → APIKeyField.js} +3 -4
  14. package/dist/components/ActionToast.d.ts +30 -0
  15. package/dist/components/{AIActionProposalList.js → ActionToast.js} +78 -8
  16. package/dist/components/{AIActionProposalList.module.css → ActionToast.module.css} +49 -5
  17. package/dist/components/AuditLogList.d.ts +32 -0
  18. package/dist/components/AuditLogList.js +67 -0
  19. package/dist/components/AuditLogList.module.css +152 -0
  20. package/dist/components/DiffDialog.d.ts +19 -0
  21. package/dist/components/DiffDialog.js +396 -0
  22. package/dist/components/DiffDialog.module.css +333 -0
  23. package/dist/components/Icons.d.ts +9 -0
  24. package/dist/components/Icons.js +193 -0
  25. package/dist/components/MentionPopover.d.ts +19 -0
  26. package/dist/components/{CollectionMentionPopover.js → MentionPopover.js} +11 -4
  27. package/dist/components/{CollectionMentionPopover.module.css → MentionPopover.module.css} +0 -2
  28. package/dist/components/hooks/useAISettings.d.ts +6 -4
  29. package/dist/components/hooks/useAISettings.js +18 -3
  30. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +3 -3
  31. package/dist/components/hooks/useDocumentMentionSuggestions.js +2 -2
  32. package/dist/exports/client.d.ts +4 -2
  33. package/dist/exports/client.js +4 -2
  34. package/dist/handlers/applyActionHandler.d.ts +8 -0
  35. package/dist/handlers/applyActionHandler.js +563 -0
  36. package/dist/handlers/auditLogHandler.d.ts +6 -0
  37. package/dist/handlers/auditLogHandler.js +39 -0
  38. package/dist/handlers/chatHandler.d.ts +42 -0
  39. package/dist/handlers/chatHandler.js +634 -0
  40. package/dist/handlers/mentionSuggestionHandler.d.ts +7 -0
  41. package/dist/handlers/mentionSuggestionHandler.js +97 -0
  42. package/dist/handlers/proposalDiffHandler.d.ts +7 -0
  43. package/dist/handlers/proposalDiffHandler.js +208 -0
  44. package/dist/index.d.ts +6 -6
  45. package/dist/index.js +151 -8
  46. package/dist/payload/collectionPermissions.d.ts +14 -13
  47. package/dist/payload/collectionPermissions.js +3 -2
  48. package/dist/payload/normalizeData.d.ts +8 -0
  49. package/dist/payload/normalizeData.js +25 -3
  50. package/dist/payload/schemaContext.d.ts +79 -9
  51. package/dist/payload/schemaContext.js +231 -22
  52. package/dist/payload/shared.d.ts +33 -0
  53. package/dist/payload/shared.js +114 -0
  54. package/package.json +7 -2
  55. package/dist/components/AIActionProposalList.d.ts +0 -25
  56. package/dist/components/AIApiKeyField.d.ts +0 -2
  57. package/dist/components/CollectionMentionPopover.d.ts +0 -16
  58. package/dist/components/hooks/utils.d.ts +0 -1
  59. package/dist/components/hooks/utils.js +0 -3
  60. package/dist/endpoints/aiApplyActionEndpointHandler.d.ts +0 -7
  61. package/dist/endpoints/aiApplyActionEndpointHandler.js +0 -169
  62. package/dist/endpoints/aiChatEndpointHandler.d.ts +0 -35
  63. package/dist/endpoints/aiChatEndpointHandler.js +0 -321
  64. package/dist/endpoints/aiMentionSuggestionsEndpointHandler.d.ts +0 -7
  65. package/dist/endpoints/aiMentionSuggestionsEndpointHandler.js +0 -63
@@ -1,19 +1,28 @@
1
1
  import type { PayloadHandler } from "payload";
2
- import { type ResolvedAICollectionPermissionMap } from "./collectionPermissions.js";
3
- export type AIChatMention = {
2
+ import { type ResolvedCollectionPermissionMap } from "./collectionPermissions.js";
3
+ export type ChatMention = {
4
4
  collection?: string;
5
5
  id?: string;
6
6
  label?: string;
7
7
  parent?: string;
8
8
  slug?: string;
9
- type?: "block" | "collection" | "doc" | "global";
9
+ type?: "block" | "collection" | "doc" | "global" | "locale";
10
10
  };
11
11
  export type FieldConfig = {
12
+ admin?: {
13
+ condition?: unknown;
14
+ };
12
15
  blocks?: BlockConfig[];
16
+ defaultValue?: unknown;
13
17
  fields?: FieldConfig[];
14
18
  hasMany?: boolean;
15
19
  label?: unknown;
20
+ localized?: boolean;
16
21
  name?: string;
22
+ options?: (string | {
23
+ label?: unknown;
24
+ value?: string;
25
+ })[];
17
26
  relationTo?: unknown;
18
27
  required?: boolean;
19
28
  type?: string;
@@ -26,27 +35,88 @@ type BlockConfig = {
26
35
  };
27
36
  slug: string;
28
37
  };
38
+ type VersionConfig = boolean | {
39
+ drafts?: boolean | Record<string, unknown>;
40
+ };
41
+ type CollectionLikeConfig = {
42
+ admin?: {
43
+ useAsTitle?: string;
44
+ };
45
+ access?: Record<string, unknown>;
46
+ auth?: unknown;
47
+ fields?: FieldConfig[];
48
+ label?: unknown;
49
+ labels?: {
50
+ plural?: unknown;
51
+ singular?: unknown;
52
+ };
53
+ slug: string;
54
+ versions?: VersionConfig;
55
+ };
29
56
  type MentionContext = {
30
57
  blockContexts: (Record<string, unknown> & {
31
58
  parent: string;
32
59
  slug: string;
33
60
  })[];
34
61
  collectionSlugs: string[];
62
+ collections?: ResolvedCollectionPermissionMap;
35
63
  globalSlugs: string[];
36
- mentions?: AIChatMention[];
64
+ mentions?: ChatMention[];
37
65
  req: Parameters<PayloadHandler>[0];
38
66
  };
39
- export declare const describeField: (field: FieldConfig) => Record<string, unknown>;
40
- export declare const collectBlocks: ({ fields, parent, }: {
67
+ export declare const describeCollectionLikeSummary: ({ config, permissions, type, }: {
68
+ config: CollectionLikeConfig;
69
+ permissions?: ResolvedCollectionPermissionMap;
70
+ type: "collection" | "global";
71
+ }) => {
72
+ access: {
73
+ aiPermissions: Record<import("./collectionPermissions.js").CollectionAction, boolean> | {
74
+ read: boolean;
75
+ update: boolean;
76
+ };
77
+ hasPayloadAccessControl: boolean;
78
+ };
79
+ hasAuth: boolean | undefined;
80
+ hasDrafts: boolean | undefined;
81
+ hasLocalizedFields: boolean | undefined;
82
+ label: unknown;
83
+ localizedFieldNames: string[] | undefined;
84
+ slug: string;
85
+ type: "collection" | "global";
86
+ useAsTitle: string | undefined;
87
+ };
88
+ export declare const describeCollectionLikeConfig: ({ config, permissions, type, }: {
89
+ config: CollectionLikeConfig;
90
+ permissions?: ResolvedCollectionPermissionMap;
91
+ type: "collection" | "global";
92
+ }) => {
93
+ fields: Record<string, unknown>[];
94
+ access: {
95
+ aiPermissions: Record<import("./collectionPermissions.js").CollectionAction, boolean> | {
96
+ read: boolean;
97
+ update: boolean;
98
+ };
99
+ hasPayloadAccessControl: boolean;
100
+ };
101
+ hasAuth: boolean | undefined;
102
+ hasDrafts: boolean | undefined;
103
+ hasLocalizedFields: boolean | undefined;
104
+ label: unknown;
105
+ localizedFieldNames: string[] | undefined;
106
+ slug: string;
107
+ type: "collection" | "global";
108
+ useAsTitle: string | undefined;
109
+ };
110
+ export declare const collectBlocks: ({ fields, parent }: {
41
111
  fields: FieldConfig[];
42
112
  parent: string;
43
113
  }) => (Record<string, unknown> & {
44
114
  parent: string;
45
115
  slug: string;
46
116
  })[];
47
- export declare const getAllowedCollectionSlugs: (req: Parameters<PayloadHandler>[0], collections?: ResolvedAICollectionPermissionMap) => string[];
48
- export declare const getMentionContext: ({ blockContexts, collectionSlugs, globalSlugs, mentions, req, }: MentionContext) => Promise<Record<string, unknown>[]>;
49
- export declare const buildPromptWithMentionContext: ({ mentionContext, prompt, }: {
117
+ export declare const getAllowedCollectionSlugs: (req: Parameters<PayloadHandler>[0], collections?: ResolvedCollectionPermissionMap) => string[];
118
+ export declare const getMentionContext: ({ blockContexts, collectionSlugs, collections, globalSlugs, mentions, req }: MentionContext) => Promise<Record<string, unknown>[]>;
119
+ export declare const buildPromptWithMentionContext: ({ mentionContext, prompt }: {
50
120
  mentionContext: Record<string, unknown>[];
51
121
  prompt: string;
52
122
  }) => string;
@@ -1,18 +1,172 @@
1
- import { getCollectionSlugsForAction } from "./collectionPermissions.js";
2
- import { getSerializableLabel, isInternalCollection } from "./shared.js";
1
+ import { getCollectionPermissions, getCollectionSlugsForAction } from "./collectionPermissions.js";
2
+ import { getCollectionFields, isAuthCollection } from "./normalizeData.js";
3
+ import { getSerializableLabel, isInternalCollection, isRecord } from "./shared.js";
3
4
  const getSerializableRelationTo = (relationTo)=>{
4
- if (typeof relationTo === "string") {
5
- return relationTo;
6
- }
5
+ if (typeof relationTo === "string") return relationTo;
7
6
  if (Array.isArray(relationTo) && relationTo.every((item)=>typeof item === "string")) {
8
7
  return relationTo;
9
8
  }
10
9
  return undefined;
11
10
  };
12
- export const describeField = (field)=>{
11
+ const fieldDescriptionCache = new WeakMap();
12
+ const blockDescriptionCache = new WeakMap();
13
+ const collectionDescriptionCache = new WeakMap();
14
+ const getLocaleConfigs = (req)=>{
15
+ const localization = req.payload.config.localization;
16
+ const defaultLocale = localization?.defaultLocale;
17
+ const locales = localization?.locales || [];
18
+ return locales.flatMap((locale)=>{
19
+ if (typeof locale === "string") {
20
+ return [
21
+ {
22
+ code: locale,
23
+ ...locale === defaultLocale ? {
24
+ isDefault: true
25
+ } : {},
26
+ label: locale
27
+ }
28
+ ];
29
+ }
30
+ if (!isRecord(locale)) return [];
31
+ const code = "code" in locale && typeof locale.code === "string" ? locale.code : "label" in locale && typeof locale.label === "string" ? locale.label : undefined;
32
+ if (!code) return [];
33
+ const label = "label" in locale ? getSerializableLabel(locale.label, code) : code;
34
+ return [
35
+ {
36
+ code,
37
+ ...code === defaultLocale ? {
38
+ isDefault: true
39
+ } : {},
40
+ label
41
+ }
42
+ ];
43
+ });
44
+ };
45
+ const getSerializableOptions = (options)=>{
46
+ if (!Array.isArray(options)) return undefined;
47
+ const serializedOptions = options.map((option)=>{
48
+ if (typeof option === "string") {
49
+ return {
50
+ label: option,
51
+ value: option
52
+ };
53
+ }
54
+ if (!isRecord(option)) return null;
55
+ const label = "label" in option ? getSerializableLabel(option.label) : undefined;
56
+ 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);
67
+ return serializedOptions.length > 0 ? serializedOptions : undefined;
68
+ };
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
+ const toNormalizeCollectionConfig = (config)=>{
76
+ if (!config) return undefined;
77
+ return {
78
+ auth: config.auth,
79
+ fields: config.fields || [],
80
+ slug: config.slug
81
+ };
82
+ };
83
+ const getSchemaFields = (config)=>{
84
+ 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;
101
+ };
102
+ const getLocalizedFieldNames = (fields, path = "")=>{
103
+ return fields.flatMap((field)=>{
104
+ const fieldPath = field.name ? path ? `${path}.${field.name}` : field.name : path;
105
+ const nestedFields = field.fields ? getLocalizedFieldNames(field.fields, fieldPath) : [];
106
+ const nestedBlocks = field.blocks ? field.blocks.flatMap((block)=>getLocalizedFieldNames(block.fields || [], `${fieldPath}.${block.slug}`)) : [];
107
+ return [
108
+ ...field.localized && fieldPath ? [
109
+ fieldPath
110
+ ] : [],
111
+ ...nestedFields,
112
+ ...nestedBlocks
113
+ ];
114
+ });
115
+ };
116
+ const getCachedCollectionDescription = (config, type)=>{
117
+ const cached = collectionDescriptionCache.get(config);
118
+ if (cached) return cached;
119
+ const slug = config.slug;
120
+ const fields = getSchemaFields(config);
121
+ const localizedFieldNames = getLocalizedFieldNames(fields);
122
+ const description = {
123
+ fields: fields.map(describeField),
124
+ hasAuth: isAuthCollection(toNormalizeCollectionConfig(config)) || undefined,
125
+ hasDrafts: hasDrafts(config) || undefined,
126
+ hasLocalizedFields: localizedFieldNames.length > 0 || undefined,
127
+ label: type === "collection" ? config.labels?.plural || config.labels?.singular || slug : getSerializableLabel(config.label) || slug,
128
+ ...localizedFieldNames.length > 0 ? {
129
+ localizedFieldNames
130
+ } : {},
131
+ ...type === "collection" && config.admin?.useAsTitle ? {
132
+ useAsTitle: config.admin.useAsTitle
133
+ } : {},
134
+ slug
135
+ };
136
+ collectionDescriptionCache.set(config, description);
137
+ return description;
138
+ };
139
+ export const describeCollectionLikeSummary = ({ config, permissions, type })=>{
140
+ const description = getCachedCollectionDescription(config, type);
141
+ const slug = description.slug;
142
+ return {
143
+ access: {
144
+ aiPermissions: type === "collection" ? getCollectionPermissions({
145
+ permissions,
146
+ slug
147
+ }) : {
148
+ read: true,
149
+ update: true
150
+ },
151
+ hasPayloadAccessControl: Boolean(config.access)
152
+ },
153
+ hasAuth: description.hasAuth,
154
+ hasDrafts: description.hasDrafts,
155
+ hasLocalizedFields: description.hasLocalizedFields,
156
+ label: description.label,
157
+ localizedFieldNames: description.localizedFieldNames,
158
+ slug,
159
+ type,
160
+ useAsTitle: description.useAsTitle
161
+ };
162
+ };
163
+ const describeField = (field)=>{
164
+ const cached = fieldDescriptionCache.get(field);
165
+ if (cached) return cached;
13
166
  const label = getSerializableLabel(field.label);
14
167
  const relationTo = getSerializableRelationTo(field.relationTo);
15
- return {
168
+ const options = getSerializableOptions(field.options);
169
+ const description = {
16
170
  ...label ? {
17
171
  label
18
172
  } : {},
@@ -28,9 +182,18 @@ export const describeField = (field)=>{
28
182
  ...field.hasMany ? {
29
183
  hasMany: field.hasMany
30
184
  } : {},
185
+ ...field.localized ? {
186
+ localized: field.localized
187
+ } : {},
31
188
  ...relationTo ? {
32
189
  relationTo
33
190
  } : {},
191
+ ...options ? {
192
+ options
193
+ } : {},
194
+ ...field.admin?.condition ? {
195
+ hasCondition: true
196
+ } : {},
34
197
  ...field.fields ? {
35
198
  fields: field.fields.map(describeField)
36
199
  } : {},
@@ -38,13 +201,29 @@ export const describeField = (field)=>{
38
201
  blocks: field.blocks.map(describeBlock)
39
202
  } : {}
40
203
  };
204
+ fieldDescriptionCache.set(field, description);
205
+ return description;
41
206
  };
42
207
  const describeBlock = (block)=>{
43
- return {
208
+ const cached = blockDescriptionCache.get(block);
209
+ if (cached) return cached;
210
+ const description = {
44
211
  fields: (block.fields || []).map(describeField),
45
212
  label: getSerializableLabel(block.labels?.singular) || block.slug,
46
213
  slug: block.slug
47
214
  };
215
+ blockDescriptionCache.set(block, description);
216
+ return description;
217
+ };
218
+ export const describeCollectionLikeConfig = ({ config, permissions, type })=>{
219
+ return {
220
+ ...describeCollectionLikeSummary({
221
+ config,
222
+ permissions,
223
+ type
224
+ }),
225
+ fields: getCachedCollectionDescription(config, type).fields
226
+ };
48
227
  };
49
228
  export const collectBlocks = ({ fields, parent })=>{
50
229
  const blocks = [];
@@ -78,11 +257,34 @@ export const getAllowedCollectionSlugs = (req, collections)=>{
78
257
  req
79
258
  });
80
259
  };
81
- export const getMentionContext = async ({ blockContexts, collectionSlugs, globalSlugs, mentions, req })=>{
260
+ export const getMentionContext = async ({ blockContexts, collectionSlugs, collections, globalSlugs, mentions, req })=>{
82
261
  if (!mentions || mentions.length === 0) return [];
83
262
  const context = [];
84
263
  const seen = new Set();
264
+ 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);
266
+ const activeLocale = selectedLocales.at(-1);
267
+ if (localeConfigs.length > 0) {
268
+ context.push({
269
+ activeLocale,
270
+ defaultLocale: localeConfigs.find((locale)=>locale.isDefault)?.code,
271
+ locales: localeConfigs,
272
+ selectedLocales,
273
+ type: "locales"
274
+ });
275
+ }
85
276
  for (const mention of mentions.slice(0, 8)){
277
+ if (mention.type === "locale" && mention.slug) {
278
+ const locale = localeConfigs.find((item)=>item.code === mention.slug);
279
+ if (!locale) continue;
280
+ const key = `locale:${locale.code}`;
281
+ if (seen.has(key)) continue;
282
+ seen.add(key);
283
+ context.push({
284
+ ...locale,
285
+ type: "locale"
286
+ });
287
+ }
86
288
  if (mention.type === "collection" && mention.slug) {
87
289
  const slug = mention.slug;
88
290
  const key = `collection:${slug}`;
@@ -90,12 +292,11 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, global
90
292
  const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === slug);
91
293
  if (!collectionConfig) continue;
92
294
  seen.add(key);
93
- context.push({
94
- fields: collectionConfig.fields.map(describeField),
95
- label: collectionConfig.labels?.plural || collectionConfig.labels?.singular || slug,
96
- slug,
295
+ context.push(describeCollectionLikeConfig({
296
+ config: collectionConfig,
297
+ permissions: collections,
97
298
  type: "collection"
98
- });
299
+ }));
99
300
  }
100
301
  if (mention.type === "global" && mention.slug) {
101
302
  const slug = mention.slug;
@@ -105,17 +306,20 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, global
105
306
  if (!globalConfig) continue;
106
307
  const globalDoc = await req.payload.findGlobal({
107
308
  depth: 2,
309
+ ...activeLocale ? {
310
+ locale: activeLocale
311
+ } : {},
108
312
  overrideAccess: false,
109
313
  req,
110
314
  slug: slug
111
315
  }).catch(()=>null);
112
316
  seen.add(key);
113
317
  context.push({
114
- doc: globalDoc,
115
- fields: globalConfig.fields.map(describeField),
116
- label: globalConfig.label || slug,
117
- slug,
118
- type: "global"
318
+ ...describeCollectionLikeConfig({
319
+ config: globalConfig,
320
+ type: "global"
321
+ }),
322
+ doc: globalDoc
119
323
  });
120
324
  }
121
325
  if (mention.type === "block" && mention.slug) {
@@ -138,6 +342,9 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, global
138
342
  collection: slug,
139
343
  depth: 2,
140
344
  id: mention.id,
345
+ ...activeLocale ? {
346
+ locale: activeLocale
347
+ } : {},
141
348
  overrideAccess: false,
142
349
  req
143
350
  }).catch(()=>null);
@@ -148,6 +355,9 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, global
148
355
  doc,
149
356
  id: mention.id,
150
357
  label: mention.label || mention.id,
358
+ ...activeLocale ? {
359
+ locale: activeLocale
360
+ } : {},
151
361
  type: "doc"
152
362
  });
153
363
  }
@@ -157,9 +367,8 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, global
157
367
  export const buildPromptWithMentionContext = ({ mentionContext, prompt })=>{
158
368
  if (mentionContext.length === 0) return prompt;
159
369
  return [
160
- "The user selected the following Payload CMS references in the input. Treat inline text like `collection: Name` or `document: Name` as references to this context, not as literal content.",
161
- JSON.stringify(mentionContext, null, 2),
162
- "User request:",
370
+ "Selected Payload references JSON. Treat inline reference labels as these entities:",
371
+ JSON.stringify(mentionContext),
163
372
  prompt
164
373
  ].join("\n\n");
165
374
  };
@@ -1,2 +1,35 @@
1
+ import type { PayloadHandler } from "payload";
2
+ export type LocalizedDataInput = Record<string, Record<string, unknown>>;
3
+ export type AIActionProposalLike = {
4
+ action: "create" | "delete" | "update" | "updateGlobal";
5
+ collection?: string;
6
+ data?: Record<string, unknown>;
7
+ id?: string;
8
+ label: string;
9
+ locale?: string;
10
+ localizedData?: LocalizedDataInput;
11
+ slug?: string;
12
+ };
1
13
  export declare const getSerializableLabel: (label: unknown, fallback?: string) => string;
2
14
  export declare const isInternalCollection: (slug: string) => boolean;
15
+ export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
16
+ export declare const mergeData: (current: Record<string, unknown>, next: Record<string, unknown>) => Record<string, unknown>;
17
+ export declare const isKnownGlobal: (req: Parameters<PayloadHandler>[0], slug: string) => boolean;
18
+ export declare const hasLocalizedData: <Value>(value: Value) => value is Value & {
19
+ localizedData: LocalizedDataInput;
20
+ };
21
+ export declare const getDefaultLocale: (req: Parameters<PayloadHandler>[0]) => string | null;
22
+ export declare const getJSONLineKey: (line: string) => string | null;
23
+ export declare const isActionProposal: (proposal: unknown) => proposal is AIActionProposalLike;
24
+ export declare const getOptionalNumber: (value: unknown) => number | undefined;
25
+ export declare const getNumber: (value: unknown) => number;
26
+ export declare const getString: (value: unknown) => string | null;
27
+ export declare const getDocLabel: (doc: Record<string, unknown>, useAsTitle?: string) => string;
28
+ export declare const getOptionValue: (option?: string | {
29
+ label?: unknown;
30
+ value?: string;
31
+ }) => string | undefined;
32
+ export declare const hasValueAtPath: (data: Record<string, unknown>, path: string) => boolean;
33
+ export declare const getSafeProposalLabel: (label: string) => string;
34
+ export declare const setValueAtPath: (data: Record<string, unknown>, path: string, value: unknown) => void;
35
+ export declare const isAbortError: (err: unknown) => boolean;
@@ -1,3 +1,4 @@
1
+ const maxProposalLabelLength = 90;
1
2
  export const getSerializableLabel = (label, fallback = "")=>{
2
3
  if (typeof label === "string") return label;
3
4
  if (label && typeof label === "object") {
@@ -9,3 +10,116 @@ export const getSerializableLabel = (label, fallback = "")=>{
9
10
  export const isInternalCollection = (slug)=>{
10
11
  return slug.startsWith("payload-") || slug === "plugin-collection";
11
12
  };
13
+ export const isRecord = (value)=>{
14
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
15
+ };
16
+ export const mergeData = (current, next)=>{
17
+ return Object.fromEntries(Object.entries({
18
+ ...current,
19
+ ...next
20
+ }).map(([key, value])=>{
21
+ const currentValue = current[key];
22
+ if (isRecord(currentValue) && isRecord(value)) {
23
+ return [
24
+ key,
25
+ mergeData(currentValue, value)
26
+ ];
27
+ }
28
+ return [
29
+ key,
30
+ value
31
+ ];
32
+ }));
33
+ };
34
+ export const isKnownGlobal = (req, slug)=>{
35
+ return req.payload.config.globals?.some((item)=>item.slug === slug) || false;
36
+ };
37
+ export const hasLocalizedData = (value)=>{
38
+ return isRecord(value) && isRecord(value.localizedData);
39
+ };
40
+ export const getDefaultLocale = (req)=>{
41
+ const localization = req.payload.config.localization;
42
+ if (!localization) return null;
43
+ return localization.defaultLocale || null;
44
+ };
45
+ export const getJSONLineKey = (line)=>{
46
+ const match = /^(\s*)"([^"]+)":/.exec(line);
47
+ return match ? `${match[1]}${match[2]}` : null;
48
+ };
49
+ const isNonEmptyString = (value)=>{
50
+ return typeof value === "string" && value.trim().length > 0;
51
+ };
52
+ const hasWriteData = (proposal)=>{
53
+ const hasData = isRecord(proposal.data);
54
+ const hasLocalizedProposalData = hasLocalizedData(proposal);
55
+ return hasData !== hasLocalizedProposalData;
56
+ };
57
+ export const isActionProposal = (proposal)=>{
58
+ if (!isRecord(proposal) || !isNonEmptyString(proposal.label)) return false;
59
+ if (proposal.action === "create") {
60
+ return isNonEmptyString(proposal.collection) && hasWriteData(proposal);
61
+ }
62
+ if (proposal.action === "update") {
63
+ return isNonEmptyString(proposal.collection) && isNonEmptyString(proposal.id) && hasWriteData(proposal);
64
+ }
65
+ if (proposal.action === "delete") {
66
+ return isNonEmptyString(proposal.collection) && isNonEmptyString(proposal.id);
67
+ }
68
+ if (proposal.action === "updateGlobal") {
69
+ return isNonEmptyString(proposal.slug) && hasWriteData(proposal);
70
+ }
71
+ return false;
72
+ };
73
+ export const getOptionalNumber = (value)=>{
74
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
75
+ };
76
+ export const getNumber = (value)=>{
77
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
78
+ };
79
+ export const getString = (value)=>{
80
+ return typeof value === "string" ? value : null;
81
+ };
82
+ export const getDocLabel = (doc, useAsTitle)=>{
83
+ const titleField = useAsTitle ? getSerializableLabel(doc[useAsTitle]) : "";
84
+ const title = getSerializableLabel(doc.title);
85
+ const name = getSerializableLabel(doc.name);
86
+ const email = getSerializableLabel(doc.email);
87
+ return titleField || title || name || email || doc.id?.toString() || "Untitled";
88
+ };
89
+ export const getOptionValue = (option)=>{
90
+ if (!option) return undefined;
91
+ if (typeof option === "string") return option;
92
+ return typeof option.value === "string" ? option.value : undefined;
93
+ };
94
+ export const hasValueAtPath = (data, path)=>{
95
+ const segments = path.split(".");
96
+ let current = data;
97
+ for (const segment of segments){
98
+ if (!isRecord(current) || current[segment] === undefined) {
99
+ return false;
100
+ }
101
+ current = current[segment];
102
+ }
103
+ return true;
104
+ };
105
+ export const getSafeProposalLabel = (label)=>{
106
+ const firstLine = label.replace(/[*_`>#-]/g, "").split("\n").map((line)=>line.trim()).find(Boolean);
107
+ if (!firstLine) return "Review proposed CMS change";
108
+ if (firstLine.length <= maxProposalLabelLength) return firstLine;
109
+ return `${firstLine.slice(0, maxProposalLabelLength - 3).trim()}...`;
110
+ };
111
+ export const setValueAtPath = (data, path, value)=>{
112
+ const segments = path.split(".");
113
+ let current = data;
114
+ for (const segment of segments.slice(0, -1)){
115
+ const next = current[segment];
116
+ if (!isRecord(next)) {
117
+ current[segment] = {};
118
+ }
119
+ current = current[segment];
120
+ }
121
+ current[segments[segments.length - 1]] = value;
122
+ };
123
+ export const isAbortError = (err)=>{
124
+ return err instanceof DOMException && err.name === "AbortError";
125
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mvriu5/payload-ai",
3
- "version": "0.5.8",
3
+ "version": "0.5.10",
4
4
  "description": "AI assistant plugin for Payload CMS with provider selection, CMS mentions, and signed action proposals.",
5
5
  "keywords": [
6
6
  "payload",
@@ -75,7 +75,7 @@
75
75
  "payload": "3.84.1",
76
76
  "prettier": "^3.8.4",
77
77
  "react": "19.2.7",
78
- "react-dom": "19.2.6",
78
+ "react-dom": "19.2.7",
79
79
  "rimraf": "6.1.3",
80
80
  "sharp": "0.35.1",
81
81
  "typescript": "5.9.3",
@@ -94,6 +94,7 @@
94
94
  },
95
95
  "knip": {
96
96
  "entry": [
97
+ "src/exports/client.ts",
97
98
  "dev/payload.config.ts",
98
99
  "dev/next.config.mjs",
99
100
  "dev/app/**/*.{ts,tsx}"
@@ -101,6 +102,9 @@
101
102
  "ignore": [
102
103
  "dev/payload-types.ts"
103
104
  ],
105
+ "ignoreDependencies": [
106
+ "prettier"
107
+ ],
104
108
  "project": [
105
109
  "src/**/*.{ts,tsx}",
106
110
  "dev/**/*.{ts,tsx,mjs}"
@@ -111,6 +115,7 @@
111
115
  "@ai-sdk/google": "^3.0.80",
112
116
  "@ai-sdk/mistral": "^3.0.37",
113
117
  "@ai-sdk/openai": "^3.0.67",
118
+ "@openrouter/ai-sdk-provider": "^2.9.1",
114
119
  "ai": "^6.0.193",
115
120
  "zod": "^4.4.3"
116
121
  }
@@ -1,25 +0,0 @@
1
- export type AIActionProposal = {
2
- _aiSignature?: {
3
- expiresAt: string;
4
- value: string;
5
- };
6
- action: "create" | "delete" | "update" | "updateGlobal";
7
- collection?: string;
8
- data?: Record<string, unknown>;
9
- id?: string;
10
- label: string;
11
- slug?: string;
12
- };
13
- type AIActionProposalListProps = {
14
- appliedProposalIndexes: number[];
15
- description?: string;
16
- error?: string;
17
- getViewURL?: (proposal: AIActionProposal) => string | null;
18
- isApplying: boolean;
19
- onDismiss?: () => void;
20
- onDismissError?: () => void;
21
- onApply: (proposal: AIActionProposal, index: number) => void;
22
- proposals: AIActionProposal[];
23
- };
24
- export declare const AIActionProposalList: ({ appliedProposalIndexes, description, error, getViewURL, isApplying, onDismiss, onDismissError, onApply, proposals, }: AIActionProposalListProps) => import("react").JSX.Element | null;
25
- export {};