@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
@@ -0,0 +1,97 @@
1
+ import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
2
+ import { getDocLabel } from "../payload/shared.js";
3
+ const getLocaleCodes = (req)=>{
4
+ const localization = req.payload.config.localization;
5
+ if (!localization) return [];
6
+ if (Array.isArray(localization.localeCodes)) return localization.localeCodes;
7
+ const locales = localization.locales || [];
8
+ return locales.flatMap((locale)=>{
9
+ if (typeof locale === "string") return [
10
+ locale
11
+ ];
12
+ if (locale && typeof locale === "object" && "code" in locale && typeof locale.code === "string") return [
13
+ locale.code
14
+ ];
15
+ return [];
16
+ });
17
+ };
18
+ const hasLocalizedFields = (fields)=>{
19
+ return fields.some((field)=>{
20
+ return Boolean(field.localized) || Boolean(field.fields?.length && hasLocalizedFields(field.fields)) || Boolean(field.tabs?.some((tab)=>hasLocalizedFields(tab.fields || [])));
21
+ });
22
+ };
23
+ export const createMentionSuggestionHandler = (options = {})=>async (req)=>{
24
+ if (!req.user) return Response.json({
25
+ error: "Unauthorized"
26
+ }, {
27
+ status: 401
28
+ });
29
+ const body = req.json ? await req.json().catch(()=>null) : null;
30
+ const query = body?.query?.trim();
31
+ const collectionSlug = body?.collectionSlug?.trim();
32
+ if (!query && !collectionSlug) return Response.json({
33
+ suggestions: []
34
+ });
35
+ const suggestions = [];
36
+ const suggestionKeys = new Set();
37
+ const normalizedQuery = query?.toLowerCase();
38
+ const collections = req.payload.config.collections.filter((collection)=>{
39
+ if (!isCollectionActionAllowed({
40
+ action: "read",
41
+ permissions: options.collections,
42
+ req,
43
+ slug: collection.slug
44
+ })) return false;
45
+ if (collectionSlug) return collection.slug === collectionSlug;
46
+ return true;
47
+ });
48
+ const addSuggestion = ({ collectionSlug, doc, requireLabelMatch = false, useAsTitle })=>{
49
+ const id = doc.id?.toString();
50
+ if (!id) return;
51
+ const key = `${collectionSlug}:${id}`;
52
+ if (suggestionKeys.has(key)) return;
53
+ const label = getDocLabel(doc, useAsTitle);
54
+ if (requireLabelMatch && normalizedQuery && !label.toLowerCase().includes(normalizedQuery)) return;
55
+ suggestionKeys.add(key);
56
+ suggestions.push({
57
+ collection: collectionSlug,
58
+ id,
59
+ label,
60
+ slug: key,
61
+ type: "doc"
62
+ });
63
+ };
64
+ for (const collection of collections){
65
+ if (suggestions.length >= 5) break;
66
+ const collectionFields = collection.fields;
67
+ const localeCodes = query && hasLocalizedFields(collectionFields) ? getLocaleCodes(req) : [];
68
+ const localesToSearch = localeCodes.length > 0 ? localeCodes : [
69
+ null
70
+ ];
71
+ for (const locale of localesToSearch){
72
+ if (suggestions.length >= 5) break;
73
+ const result = await req.payload.find({
74
+ collection: collection.slug,
75
+ depth: 0,
76
+ limit: query ? 100 : 10,
77
+ ...locale ? {
78
+ locale
79
+ } : {},
80
+ overrideAccess: false,
81
+ req
82
+ });
83
+ for (const doc of result.docs){
84
+ if (suggestions.length >= 5) break;
85
+ addSuggestion({
86
+ collectionSlug: collection.slug,
87
+ doc,
88
+ requireLabelMatch: Boolean(normalizedQuery),
89
+ useAsTitle: collection.admin?.useAsTitle
90
+ });
91
+ }
92
+ }
93
+ }
94
+ return Response.json({
95
+ suggestions: suggestions.slice(0, 5)
96
+ });
97
+ };
@@ -0,0 +1,7 @@
1
+ import type { PayloadHandler } from "payload";
2
+ import { type ResolvedCollectionPermissionMap } from "../payload/collectionPermissions.js";
3
+ type ProposalDiffOptions = {
4
+ collections?: ResolvedCollectionPermissionMap;
5
+ };
6
+ export declare const createProposalDiffHandler: (options?: ProposalDiffOptions) => PayloadHandler;
7
+ export {};
@@ -0,0 +1,208 @@
1
+ import { verifyActionProposal } from "../ai/proposalSigning.js";
2
+ import { redactSensitiveData } from "../ai/sensitiveData.js";
3
+ import { getCollectionFields, getLocalizedRequiredFallbackData, normalizeAuthData, normalizeDataForFields } from "../payload/normalizeData.js";
4
+ import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
5
+ import { getDefaultLocale, hasLocalizedData, isActionProposal, mergeData } from "../payload/shared.js";
6
+ const applyLocalizedRequiredFallback = ({ data, fallbackSource, fields })=>{
7
+ return mergeData(getLocalizedRequiredFallbackData({
8
+ fields,
9
+ source: fallbackSource,
10
+ target: data
11
+ }), data);
12
+ };
13
+ export const createProposalDiffHandler = (options = {})=>async (req)=>{
14
+ if (!req.user) return Response.json({
15
+ error: "Unauthorized"
16
+ }, {
17
+ status: 401
18
+ });
19
+ const body = req.json ? await req.json().catch(()=>null) : null;
20
+ const proposal = body?.proposal;
21
+ if (!proposal) return Response.json({
22
+ error: "Proposal is required"
23
+ }, {
24
+ status: 400
25
+ });
26
+ if (!verifyActionProposal(proposal)) return Response.json({
27
+ error: "Proposal signature is invalid or expired."
28
+ }, {
29
+ status: 400
30
+ });
31
+ if (!isActionProposal(proposal)) return Response.json({
32
+ error: "Proposal is invalid."
33
+ }, {
34
+ status: 400
35
+ });
36
+ try {
37
+ const defaultLocale = getDefaultLocale(req);
38
+ if (proposal.action === "updateGlobal") {
39
+ const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
40
+ if (!globalConfig) return Response.json({
41
+ error: "Unknown global"
42
+ }, {
43
+ status: 400
44
+ });
45
+ if (hasLocalizedData(proposal)) {
46
+ const beforeByLocale = {};
47
+ const afterByLocale = {};
48
+ const globalFields = globalConfig.fields || [];
49
+ const defaultLocaleDoc = defaultLocale ? await req.payload.findGlobal({
50
+ depth: 2,
51
+ fallbackLocale: false,
52
+ locale: defaultLocale,
53
+ overrideAccess: false,
54
+ req,
55
+ slug: proposal.slug
56
+ }) : null;
57
+ for (const [locale, localeData] of Object.entries(proposal.localizedData)){
58
+ const normalized = normalizeDataForFields(globalFields, localeData);
59
+ const doc = await req.payload.findGlobal({
60
+ depth: 2,
61
+ fallbackLocale: false,
62
+ locale,
63
+ overrideAccess: false,
64
+ req,
65
+ slug: proposal.slug
66
+ });
67
+ const completedData = applyLocalizedRequiredFallback({
68
+ data: normalized.data,
69
+ fallbackSource: locale === defaultLocale ? doc : defaultLocaleDoc || doc,
70
+ fields: globalFields
71
+ });
72
+ beforeByLocale[locale] = redactSensitiveData(doc);
73
+ afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
74
+ }
75
+ return Response.json({
76
+ after: afterByLocale,
77
+ before: beforeByLocale
78
+ });
79
+ }
80
+ const normalized = normalizeDataForFields(globalConfig.fields || [], proposal.data);
81
+ const doc = await req.payload.findGlobal({
82
+ depth: 2,
83
+ ...proposal.locale ? {
84
+ locale: proposal.locale
85
+ } : {},
86
+ overrideAccess: false,
87
+ req,
88
+ slug: proposal.slug
89
+ });
90
+ return Response.json({
91
+ after: redactSensitiveData(mergeData(doc, normalized.data)),
92
+ before: redactSensitiveData(doc)
93
+ });
94
+ }
95
+ if (!isCollectionActionAllowed({
96
+ action: proposal.action === "delete" ? "delete" : "read",
97
+ permissions: options.collections,
98
+ req,
99
+ slug: proposal.collection
100
+ })) return Response.json({
101
+ error: "Unknown collection"
102
+ }, {
103
+ status: 400
104
+ });
105
+ if (proposal.action === "delete") {
106
+ const doc = await req.payload.findByID({
107
+ collection: proposal.collection,
108
+ depth: 2,
109
+ id: proposal.id,
110
+ ...proposal.locale ? {
111
+ locale: proposal.locale
112
+ } : {},
113
+ overrideAccess: false,
114
+ req
115
+ });
116
+ return Response.json({
117
+ after: {},
118
+ before: redactSensitiveData(doc)
119
+ });
120
+ }
121
+ const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
122
+ const collectionFields = getCollectionFields(collectionConfig);
123
+ const normalizeCollectionData = (data)=>normalizeAuthData(collectionConfig, normalizeDataForFields(collectionFields, data));
124
+ if (hasLocalizedData(proposal)) {
125
+ const afterByLocale = {};
126
+ const beforeByLocale = {};
127
+ const defaultLocaleDoc = proposal.action === "update" && defaultLocale ? await req.payload.findByID({
128
+ collection: proposal.collection,
129
+ depth: 2,
130
+ fallbackLocale: false,
131
+ id: proposal.id,
132
+ locale: defaultLocale,
133
+ overrideAccess: false,
134
+ req
135
+ }) : null;
136
+ let createFallbackSource = null;
137
+ for (const [locale, localeData] of Object.entries(proposal.localizedData)){
138
+ const normalized = normalizeCollectionData(localeData);
139
+ const fallbackSource = proposal.action === "create" ? createFallbackSource || {} : locale === defaultLocale ? await req.payload.findByID({
140
+ collection: proposal.collection,
141
+ depth: 2,
142
+ fallbackLocale: false,
143
+ id: proposal.id,
144
+ locale,
145
+ overrideAccess: false,
146
+ req
147
+ }) : defaultLocaleDoc || {};
148
+ const completedData = applyLocalizedRequiredFallback({
149
+ data: normalized.data,
150
+ fallbackSource,
151
+ fields: collectionFields
152
+ });
153
+ if (proposal.action === "create") {
154
+ beforeByLocale[locale] = {};
155
+ afterByLocale[locale] = redactSensitiveData(completedData);
156
+ createFallbackSource = mergeData(createFallbackSource || {}, completedData);
157
+ continue;
158
+ }
159
+ const doc = await req.payload.findByID({
160
+ collection: proposal.collection,
161
+ depth: 2,
162
+ fallbackLocale: false,
163
+ id: proposal.id,
164
+ locale,
165
+ overrideAccess: false,
166
+ req
167
+ });
168
+ beforeByLocale[locale] = redactSensitiveData(doc);
169
+ afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
170
+ }
171
+ return Response.json({
172
+ after: afterByLocale,
173
+ before: beforeByLocale
174
+ });
175
+ }
176
+ const normalized = normalizeCollectionData(proposal.data);
177
+ if (proposal.action === "create") {
178
+ return Response.json({
179
+ after: redactSensitiveData(normalized.data),
180
+ before: {}
181
+ });
182
+ }
183
+ const doc = await req.payload.findByID({
184
+ collection: proposal.collection,
185
+ depth: 2,
186
+ id: proposal.id,
187
+ ...proposal.locale ? {
188
+ locale: proposal.locale
189
+ } : {},
190
+ overrideAccess: false,
191
+ req
192
+ });
193
+ return Response.json({
194
+ after: redactSensitiveData(mergeData(doc, normalized.data)),
195
+ before: redactSensitiveData(doc)
196
+ });
197
+ } catch (err) {
198
+ req.payload.logger.error({
199
+ err,
200
+ msg: "AI proposal diff failed"
201
+ });
202
+ return Response.json({
203
+ error: "Could not load proposal diff."
204
+ }, {
205
+ status: 400
206
+ });
207
+ }
208
+ };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import type { CollectionSlug, Config } from "payload";
1
+ import type { Config } from "payload";
2
2
  import { type AIModelConfig } from "./ai/providerOptions.js";
3
- import { type AICollectionPermissionConfig } from "./payload/collectionPermissions.js";
4
- export type PayloadAiPluginOptions = {
3
+ import { type CollectionPermissionMap } from "./payload/collectionPermissions.js";
4
+ export type PayloadAIPluginOptions = {
5
5
  allowUserApiKeys?: boolean;
6
- collections?: Partial<Record<CollectionSlug, AICollectionPermissionConfig>>;
6
+ collections?: CollectionPermissionMap;
7
7
  disabled?: boolean;
8
+ maxOutputTokens?: number;
8
9
  models?: AIModelConfig;
9
10
  };
10
- export type PayloadAiPluginConfig = PayloadAiPluginOptions;
11
- export declare const payloadAiPlugin: (pluginOptions: PayloadAiPluginOptions) => (config: Config) => Config;
11
+ export declare const payloadAiPlugin: (pluginOptions: PayloadAIPluginOptions) => (config: Config) => Config;
package/dist/index.js CHANGED
@@ -1,9 +1,130 @@
1
1
  import { aiProviders, getResolvedAIModelConfig } from "./ai/providerOptions.js";
2
- import { createAIApplyActionEndpointHandler } from "./endpoints/aiApplyActionEndpointHandler.js";
3
- import { createAIChatEndpointHandler } from "./endpoints/aiChatEndpointHandler.js";
4
- import { createAIMentionSuggestionsEndpointHandler } from "./endpoints/aiMentionSuggestionsEndpointHandler.js";
2
+ import { createApplyActionHandler } from "./handlers/applyActionHandler.js";
3
+ import { createChatHandler } from "./handlers/chatHandler.js";
4
+ import { createMentionSuggestionHandler } from "./handlers/mentionSuggestionHandler.js";
5
+ import { createProposalDiffHandler } from "./handlers/proposalDiffHandler.js";
6
+ import { createAuditLogHandler } from "./handlers/auditLogHandler.js";
5
7
  import { resolveCollectionPermissions } from "./payload/collectionPermissions.js";
6
8
  import { isInternalCollection } from "./payload/shared.js";
9
+ const auditLogCollectionSlug = "payload-ai-auditlog";
10
+ const createAIChangesCollection = ()=>({
11
+ slug: auditLogCollectionSlug,
12
+ access: {
13
+ create: ()=>false,
14
+ delete: ()=>false,
15
+ read: ({ req })=>Boolean(req.user),
16
+ update: ()=>false
17
+ },
18
+ admin: {
19
+ defaultColumns: [
20
+ "title",
21
+ "action",
22
+ "additions",
23
+ "removals",
24
+ "createdAt"
25
+ ],
26
+ group: "AI",
27
+ useAsTitle: "title"
28
+ },
29
+ labels: {
30
+ plural: "AI Changes",
31
+ singular: "AI Change"
32
+ },
33
+ fields: [
34
+ {
35
+ name: "title",
36
+ type: "text",
37
+ required: true
38
+ },
39
+ {
40
+ name: "action",
41
+ type: "select",
42
+ options: [
43
+ "create",
44
+ "update",
45
+ "delete",
46
+ "updateGlobal"
47
+ ],
48
+ required: true
49
+ },
50
+ {
51
+ name: "targetType",
52
+ type: "select",
53
+ options: [
54
+ "collection",
55
+ "global"
56
+ ],
57
+ required: true
58
+ },
59
+ {
60
+ name: "collection",
61
+ type: "text"
62
+ },
63
+ {
64
+ name: "slug",
65
+ type: "text"
66
+ },
67
+ {
68
+ name: "documentID",
69
+ type: "text"
70
+ },
71
+ {
72
+ name: "targetURL",
73
+ type: "text"
74
+ },
75
+ {
76
+ name: "additions",
77
+ type: "number",
78
+ defaultValue: 0
79
+ },
80
+ {
81
+ name: "removals",
82
+ type: "number",
83
+ defaultValue: 0
84
+ },
85
+ {
86
+ name: "before",
87
+ type: "json"
88
+ },
89
+ {
90
+ name: "after",
91
+ type: "json"
92
+ },
93
+ {
94
+ name: "proposal",
95
+ type: "json"
96
+ },
97
+ {
98
+ name: "prompt",
99
+ type: "textarea"
100
+ },
101
+ {
102
+ name: "inputTokens",
103
+ type: "number"
104
+ },
105
+ {
106
+ name: "outputTokens",
107
+ type: "number"
108
+ },
109
+ {
110
+ name: "totalTokens",
111
+ type: "number"
112
+ },
113
+ {
114
+ name: "aiResponse",
115
+ type: "textarea"
116
+ },
117
+ {
118
+ name: "userID",
119
+ type: "text"
120
+ },
121
+ {
122
+ name: "userLabel",
123
+ type: "text"
124
+ }
125
+ ],
126
+ timestamps: true
127
+ });
7
128
  const addAccountFields = ({ allowUserApiKeys, config })=>{
8
129
  const adminUserSlug = config.admin?.user;
9
130
  if (!adminUserSlug || !config.collections) return;
@@ -13,6 +134,7 @@ const addAccountFields = ({ allowUserApiKeys, config })=>{
13
134
  name: "aiProvider",
14
135
  type: "select",
15
136
  defaultValue: "openai",
137
+ label: "AI Provider",
16
138
  options: aiProviders
17
139
  });
18
140
  if (allowUserApiKeys) {
@@ -24,7 +146,8 @@ const addAccountFields = ({ allowUserApiKeys, config })=>{
24
146
  Field: "payload-ai-plugin/client#AIApiKeyField"
25
147
  },
26
148
  description: "Optional. If empty, the plugin uses the provider API key from environment variables."
27
- }
149
+ },
150
+ label: "AI API Key"
28
151
  });
29
152
  }
30
153
  };
@@ -33,7 +156,11 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
33
156
  const collectionPermissions = resolveCollectionPermissions(pluginOptions.collections);
34
157
  const allowUserApiKeys = pluginOptions.allowUserApiKeys !== false;
35
158
  const modelConfig = getResolvedAIModelConfig(pluginOptions.models);
159
+ const maxOutputTokens = typeof pluginOptions.maxOutputTokens === "number" && Number.isFinite(pluginOptions.maxOutputTokens) && pluginOptions.maxOutputTokens > 0 ? Math.floor(pluginOptions.maxOutputTokens) : undefined;
36
160
  if (!config.collections) config.collections = [];
161
+ if (!config.collections.some((collection)=>collection.slug === auditLogCollectionSlug)) {
162
+ config.collections.push(createAIChangesCollection());
163
+ }
37
164
  addAccountFields({
38
165
  allowUserApiKeys,
39
166
  config
@@ -55,27 +182,43 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
55
182
  if (!config.admin.components.beforeDashboard) config.admin.components.beforeDashboard = [];
56
183
  config.admin.components.beforeDashboard.push(`payload-ai-plugin/client#AIInput`);
57
184
  config.endpoints.push({
58
- handler: createAIChatEndpointHandler({
185
+ handler: createChatHandler({
59
186
  allowUserApiKeys,
60
187
  collections: collectionPermissions,
188
+ maxOutputTokens,
61
189
  models: modelConfig
62
190
  }),
63
191
  method: "post",
64
192
  path: "/ai-chat"
65
193
  });
66
194
  config.endpoints.push({
67
- handler: createAIApplyActionEndpointHandler({
195
+ handler: createApplyActionHandler({
196
+ changeLogCollection: auditLogCollectionSlug,
68
197
  collections: collectionPermissions
69
198
  }),
70
199
  method: "post",
71
200
  path: "/ai-apply-action"
72
201
  });
73
202
  config.endpoints.push({
74
- handler: createAIMentionSuggestionsEndpointHandler({
203
+ handler: createAuditLogHandler({
204
+ changeLogCollection: auditLogCollectionSlug
205
+ }),
206
+ method: "get",
207
+ path: "/ai-audit-log"
208
+ });
209
+ config.endpoints.push({
210
+ handler: createProposalDiffHandler({
211
+ collections: collectionPermissions
212
+ }),
213
+ method: "post",
214
+ path: "/ai-proposal-diff"
215
+ });
216
+ config.endpoints.push({
217
+ handler: createMentionSuggestionHandler({
75
218
  collections: collectionPermissions
76
219
  }),
77
220
  method: "post",
78
- path: "/ai-mention-suggestions"
221
+ path: "/ai-mention-suggestion"
79
222
  });
80
223
  if (incomingOnInit) {
81
224
  config.onInit = async (payload)=>{
@@ -1,22 +1,23 @@
1
1
  import type { PayloadHandler } from "payload";
2
- export type AICollectionAction = "create" | "delete" | "read" | "update";
3
- export type AICollectionPermissions = Partial<Record<AICollectionAction, boolean>>;
4
- export type AICollectionPermissionConfig = true | AICollectionPermissions;
5
- export type AICollectionPermissionMap = Partial<Record<string, AICollectionPermissionConfig>>;
6
- export type ResolvedAICollectionPermissionMap = Partial<Record<string, Record<AICollectionAction, boolean>>>;
7
- export declare const resolveCollectionPermissions: (collections?: AICollectionPermissionMap) => ResolvedAICollectionPermissionMap | undefined;
2
+ export type CollectionAction = "create" | "delete" | "read" | "update";
3
+ type CollectionPermissions = Partial<Record<CollectionAction, boolean>>;
4
+ type CollectionPermissionConfig = true | CollectionPermissions;
5
+ export type CollectionPermissionMap = Partial<Record<string, CollectionPermissionConfig>>;
6
+ export type ResolvedCollectionPermissionMap = Partial<Record<string, Record<CollectionAction, boolean>>>;
7
+ export declare const resolveCollectionPermissions: (collections?: CollectionPermissionMap) => ResolvedCollectionPermissionMap | undefined;
8
8
  export declare const getCollectionSlugsForAction: ({ action, permissions, req, }: {
9
- action: AICollectionAction;
10
- permissions?: ResolvedAICollectionPermissionMap;
9
+ action: CollectionAction;
10
+ permissions?: ResolvedCollectionPermissionMap;
11
11
  req: Parameters<PayloadHandler>[0];
12
12
  }) => string[];
13
13
  export declare const isCollectionActionAllowed: ({ action, permissions, req, slug, }: {
14
- action: AICollectionAction;
15
- permissions?: ResolvedAICollectionPermissionMap;
14
+ action: CollectionAction;
15
+ permissions?: ResolvedCollectionPermissionMap;
16
16
  req: Parameters<PayloadHandler>[0];
17
17
  slug: string;
18
18
  }) => boolean;
19
- export declare const getCollectionPermissions: ({ permissions, slug, }: {
20
- permissions?: ResolvedAICollectionPermissionMap;
19
+ export declare const getCollectionPermissions: ({ permissions, slug }: {
20
+ permissions?: ResolvedCollectionPermissionMap;
21
21
  slug: string;
22
- }) => Record<AICollectionAction, boolean>;
22
+ }) => Record<CollectionAction, boolean>;
23
+ export {};
@@ -10,7 +10,7 @@ const getKnownCollectionSlugs = (req)=>{
10
10
  };
11
11
  export const resolveCollectionPermissions = (collections)=>{
12
12
  if (!collections) return undefined;
13
- return Object.fromEntries(Object.entries(collections).map(([slug, config])=>{
13
+ const entries = Object.entries(collections).map(([slug, config])=>{
14
14
  if (config === true) {
15
15
  return [
16
16
  slug,
@@ -42,7 +42,8 @@ export const resolveCollectionPermissions = (collections)=>{
42
42
  update: Boolean(config.update)
43
43
  }
44
44
  ];
45
- }));
45
+ });
46
+ return Object.fromEntries(entries);
46
47
  };
47
48
  export const getCollectionSlugsForAction = ({ action, permissions, req })=>{
48
49
  const knownSlugs = getKnownCollectionSlugs(req);
@@ -2,10 +2,13 @@ export type FieldConfig = {
2
2
  blocks?: BlockConfig[];
3
3
  defaultValue?: unknown;
4
4
  fields?: FieldConfig[];
5
+ label?: unknown;
6
+ localized?: boolean;
5
7
  name?: string;
6
8
  options?: (string | {
7
9
  value?: string;
8
10
  })[];
11
+ required?: boolean;
9
12
  type?: string;
10
13
  };
11
14
  type BlockConfig = {
@@ -26,4 +29,9 @@ export declare const isAuthCollection: (collectionConfig?: CollectionConfig | nu
26
29
  export declare const getCollectionFields: (collectionConfig?: CollectionConfig | null) => FieldConfig[];
27
30
  export declare const normalizeAuthData: (collectionConfig: CollectionConfig | undefined, normalized: NormalizedData) => NormalizedData;
28
31
  export declare const normalizeDataForFields: (fields: FieldConfig[], data: Record<string, unknown>) => NormalizedData;
32
+ export declare const getLocalizedRequiredFallbackData: ({ fields, source, target, }: {
33
+ fields: FieldConfig[];
34
+ source: Record<string, unknown>;
35
+ target: Record<string, unknown>;
36
+ }) => Record<string, unknown>;
29
37
  export {};
@@ -1,7 +1,5 @@
1
+ import { isRecord } from "./shared.js";
1
2
  const SKIP_FIELD = Symbol("skipField");
2
- const isRecord = (value)=>{
3
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4
- };
5
3
  const getNamedFields = (fields)=>{
6
4
  return fields.filter((field)=>Boolean(field.name));
7
5
  };
@@ -190,3 +188,27 @@ export const normalizeDataForFields = (fields, data)=>{
190
188
  droppedFields
191
189
  };
192
190
  };
191
+ export const getLocalizedRequiredFallbackData = ({ fields, source, target })=>{
192
+ const fallbackData = {};
193
+ for (const field of getNamedFields(fields)){
194
+ const targetValue = target[field.name];
195
+ const sourceValue = source[field.name];
196
+ if (field.localized && field.required) {
197
+ if (targetValue === undefined && sourceValue !== undefined) {
198
+ fallbackData[field.name] = sourceValue;
199
+ }
200
+ continue;
201
+ }
202
+ if (field.type === "group" && field.fields?.length && isRecord(sourceValue)) {
203
+ const nestedFallback = getLocalizedRequiredFallbackData({
204
+ fields: field.fields,
205
+ source: sourceValue,
206
+ target: isRecord(targetValue) ? targetValue : {}
207
+ });
208
+ if (Object.keys(nestedFallback).length > 0) {
209
+ fallbackData[field.name] = nestedFallback;
210
+ }
211
+ }
212
+ }
213
+ return fallbackData;
214
+ };