@mvriu5/payload-ai 0.5.9 → 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 (69) 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 +210 -39
  11. package/dist/components/AIInput.module.css +15 -1
  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} +10 -8
  16. package/dist/components/AuditLogList.d.ts +32 -0
  17. package/dist/components/{RecentChangesList.js → AuditLogList.js} +13 -12
  18. package/dist/components/{RecentChangesList.module.css → AuditLogList.module.css} +18 -1
  19. package/dist/components/DiffDialog.d.ts +10 -3
  20. package/dist/components/DiffDialog.js +164 -78
  21. package/dist/components/DiffDialog.module.css +86 -2
  22. package/dist/components/Icons.d.ts +5 -0
  23. package/dist/components/Icons.js +109 -3
  24. package/dist/components/MentionPopover.d.ts +19 -0
  25. package/dist/components/{CollectionMentionPopover.js → MentionPopover.js} +11 -4
  26. package/dist/components/{CollectionMentionPopover.module.css → MentionPopover.module.css} +0 -2
  27. package/dist/components/hooks/useAISettings.d.ts +6 -4
  28. package/dist/components/hooks/useAISettings.js +18 -3
  29. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +3 -3
  30. package/dist/components/hooks/useDocumentMentionSuggestions.js +2 -2
  31. package/dist/exports/client.d.ts +4 -2
  32. package/dist/exports/client.js +4 -2
  33. package/dist/handlers/applyActionHandler.d.ts +8 -0
  34. package/dist/handlers/applyActionHandler.js +563 -0
  35. package/dist/handlers/auditLogHandler.d.ts +6 -0
  36. package/dist/{endpoints/aiRecentChangesEndpointHandler.js → handlers/auditLogHandler.js} +12 -8
  37. package/dist/handlers/chatHandler.d.ts +42 -0
  38. package/dist/handlers/chatHandler.js +634 -0
  39. package/dist/handlers/mentionSuggestionHandler.d.ts +7 -0
  40. package/dist/handlers/mentionSuggestionHandler.js +97 -0
  41. package/dist/handlers/proposalDiffHandler.d.ts +7 -0
  42. package/dist/handlers/proposalDiffHandler.js +208 -0
  43. package/dist/index.d.ts +6 -6
  44. package/dist/index.js +50 -18
  45. package/dist/payload/collectionPermissions.d.ts +14 -13
  46. package/dist/payload/collectionPermissions.js +3 -2
  47. package/dist/payload/normalizeData.d.ts +8 -0
  48. package/dist/payload/normalizeData.js +25 -3
  49. package/dist/payload/schemaContext.d.ts +43 -16
  50. package/dist/payload/schemaContext.js +139 -18
  51. package/dist/payload/shared.d.ts +33 -0
  52. package/dist/payload/shared.js +114 -0
  53. package/package.json +7 -2
  54. package/dist/components/AIActionProposalList.d.ts +0 -26
  55. package/dist/components/AIApiKeyField.d.ts +0 -2
  56. package/dist/components/CollectionMentionPopover.d.ts +0 -16
  57. package/dist/components/RecentChangesList.d.ts +0 -18
  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 -8
  61. package/dist/endpoints/aiApplyActionEndpointHandler.js +0 -365
  62. package/dist/endpoints/aiChatEndpointHandler.d.ts +0 -35
  63. package/dist/endpoints/aiChatEndpointHandler.js +0 -382
  64. package/dist/endpoints/aiMentionSuggestionsEndpointHandler.d.ts +0 -7
  65. package/dist/endpoints/aiMentionSuggestionsEndpointHandler.js +0 -63
  66. package/dist/endpoints/aiProposalDiffEndpointHandler.d.ts +0 -7
  67. package/dist/endpoints/aiProposalDiffEndpointHandler.js +0 -138
  68. package/dist/endpoints/aiRecentChangesEndpointHandler.d.ts +0 -6
  69. /package/dist/components/{AIActionProposalList.module.css → ActionToast.module.css} +0 -0
@@ -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,14 +1,14 @@
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";
5
- import { createAIProposalDiffEndpointHandler } from "./endpoints/aiProposalDiffEndpointHandler.js";
6
- import { createAIRecentChangesEndpointHandler } from "./endpoints/aiRecentChangesEndpointHandler.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";
7
7
  import { resolveCollectionPermissions } from "./payload/collectionPermissions.js";
8
8
  import { isInternalCollection } from "./payload/shared.js";
9
- const aiChangesCollectionSlug = "payload-ai-changes";
9
+ const auditLogCollectionSlug = "payload-ai-auditlog";
10
10
  const createAIChangesCollection = ()=>({
11
- slug: aiChangesCollectionSlug,
11
+ slug: auditLogCollectionSlug,
12
12
  access: {
13
13
  create: ()=>false,
14
14
  delete: ()=>false,
@@ -26,6 +26,10 @@ const createAIChangesCollection = ()=>({
26
26
  group: "AI",
27
27
  useAsTitle: "title"
28
28
  },
29
+ labels: {
30
+ plural: "AI Changes",
31
+ singular: "AI Change"
32
+ },
29
33
  fields: [
30
34
  {
31
35
  name: "title",
@@ -90,9 +94,33 @@ const createAIChangesCollection = ()=>({
90
94
  name: "proposal",
91
95
  type: "json"
92
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
+ },
93
117
  {
94
118
  name: "userID",
95
119
  type: "text"
120
+ },
121
+ {
122
+ name: "userLabel",
123
+ type: "text"
96
124
  }
97
125
  ],
98
126
  timestamps: true
@@ -106,6 +134,7 @@ const addAccountFields = ({ allowUserApiKeys, config })=>{
106
134
  name: "aiProvider",
107
135
  type: "select",
108
136
  defaultValue: "openai",
137
+ label: "AI Provider",
109
138
  options: aiProviders
110
139
  });
111
140
  if (allowUserApiKeys) {
@@ -117,7 +146,8 @@ const addAccountFields = ({ allowUserApiKeys, config })=>{
117
146
  Field: "payload-ai-plugin/client#AIApiKeyField"
118
147
  },
119
148
  description: "Optional. If empty, the plugin uses the provider API key from environment variables."
120
- }
149
+ },
150
+ label: "AI API Key"
121
151
  });
122
152
  }
123
153
  };
@@ -126,8 +156,9 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
126
156
  const collectionPermissions = resolveCollectionPermissions(pluginOptions.collections);
127
157
  const allowUserApiKeys = pluginOptions.allowUserApiKeys !== false;
128
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;
129
160
  if (!config.collections) config.collections = [];
130
- if (!config.collections.some((collection)=>collection.slug === aiChangesCollectionSlug)) {
161
+ if (!config.collections.some((collection)=>collection.slug === auditLogCollectionSlug)) {
131
162
  config.collections.push(createAIChangesCollection());
132
163
  }
133
164
  addAccountFields({
@@ -151,42 +182,43 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
151
182
  if (!config.admin.components.beforeDashboard) config.admin.components.beforeDashboard = [];
152
183
  config.admin.components.beforeDashboard.push(`payload-ai-plugin/client#AIInput`);
153
184
  config.endpoints.push({
154
- handler: createAIChatEndpointHandler({
185
+ handler: createChatHandler({
155
186
  allowUserApiKeys,
156
187
  collections: collectionPermissions,
188
+ maxOutputTokens,
157
189
  models: modelConfig
158
190
  }),
159
191
  method: "post",
160
192
  path: "/ai-chat"
161
193
  });
162
194
  config.endpoints.push({
163
- handler: createAIApplyActionEndpointHandler({
164
- changeLogCollection: aiChangesCollectionSlug,
195
+ handler: createApplyActionHandler({
196
+ changeLogCollection: auditLogCollectionSlug,
165
197
  collections: collectionPermissions
166
198
  }),
167
199
  method: "post",
168
200
  path: "/ai-apply-action"
169
201
  });
170
202
  config.endpoints.push({
171
- handler: createAIRecentChangesEndpointHandler({
172
- changeLogCollection: aiChangesCollectionSlug
203
+ handler: createAuditLogHandler({
204
+ changeLogCollection: auditLogCollectionSlug
173
205
  }),
174
206
  method: "get",
175
- path: "/ai-recent-changes"
207
+ path: "/ai-audit-log"
176
208
  });
177
209
  config.endpoints.push({
178
- handler: createAIProposalDiffEndpointHandler({
210
+ handler: createProposalDiffHandler({
179
211
  collections: collectionPermissions
180
212
  }),
181
213
  method: "post",
182
214
  path: "/ai-proposal-diff"
183
215
  });
184
216
  config.endpoints.push({
185
- handler: createAIMentionSuggestionsEndpointHandler({
217
+ handler: createMentionSuggestionHandler({
186
218
  collections: collectionPermissions
187
219
  }),
188
220
  method: "post",
189
- path: "/ai-mention-suggestions"
221
+ path: "/ai-mention-suggestion"
190
222
  });
191
223
  if (incomingOnInit) {
192
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
+ };