@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
@@ -1,365 +0,0 @@
1
- import { verifyAIActionProposal } from "../ai/proposals.js";
2
- import { containsSensitiveData, redactSensitiveData } from "../ai/sensitiveData.js";
3
- import { getCollectionFields, isAuthCollection, normalizeAuthData, normalizeDataForFields } from "../payload/normalizeData.js";
4
- import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
5
- const getProposalMeta = (proposal)=>{
6
- if (!proposal) return {};
7
- return {
8
- action: proposal.action,
9
- collection: "collection" in proposal ? proposal.collection : undefined,
10
- id: "id" in proposal ? proposal.id : undefined,
11
- slug: "slug" in proposal ? proposal.slug : undefined
12
- };
13
- };
14
- const getAppliedDocReference = (doc)=>{
15
- return doc?.id === undefined ? undefined : {
16
- id: doc.id
17
- };
18
- };
19
- const isAllowedCollection = (req, collection, collections, action = "read")=>{
20
- return isCollectionActionAllowed({
21
- action,
22
- permissions: collections,
23
- req,
24
- slug: collection
25
- });
26
- };
27
- const isKnownGlobal = (req, slug)=>{
28
- return req.payload.config.globals?.some((item)=>item.slug === slug) || false;
29
- };
30
- const isRecord = (value)=>{
31
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
32
- };
33
- const mergeData = (current, next)=>{
34
- return Object.fromEntries(Object.entries({
35
- ...current,
36
- ...next
37
- }).map(([key, value])=>{
38
- const currentValue = current[key];
39
- if (isRecord(currentValue) && isRecord(value)) {
40
- return [
41
- key,
42
- mergeData(currentValue, value)
43
- ];
44
- }
45
- return [
46
- key,
47
- value
48
- ];
49
- }));
50
- };
51
- const getJSONLineKey = (line)=>{
52
- const match = /^(\s*)"([^"]+)":/.exec(line);
53
- return match ? `${match[1]}${match[2]}` : null;
54
- };
55
- const countDiffChanges = ({ after, before })=>{
56
- const beforeLines = JSON.stringify(before, null, 2).split("\n");
57
- const afterLines = JSON.stringify(after, null, 2).split("\n");
58
- const dp = Array.from({
59
- length: beforeLines.length + 1
60
- }, ()=>Array(afterLines.length + 1).fill(0));
61
- let additions = 0;
62
- let removals = 0;
63
- for(let i = beforeLines.length - 1; i >= 0; i -= 1){
64
- for(let j = afterLines.length - 1; j >= 0; j -= 1){
65
- dp[i][j] = beforeLines[i] === afterLines[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
66
- }
67
- }
68
- let beforeIndex = 0;
69
- let afterIndex = 0;
70
- while(beforeIndex < beforeLines.length && afterIndex < afterLines.length){
71
- if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
72
- beforeIndex += 1;
73
- afterIndex += 1;
74
- continue;
75
- }
76
- const beforeKey = getJSONLineKey(beforeLines[beforeIndex]);
77
- const afterKey = getJSONLineKey(afterLines[afterIndex]);
78
- if (beforeKey && beforeKey === afterKey) {
79
- removals += 1;
80
- additions += 1;
81
- beforeIndex += 1;
82
- afterIndex += 1;
83
- continue;
84
- }
85
- if (dp[beforeIndex + 1][afterIndex] >= dp[beforeIndex][afterIndex + 1]) {
86
- removals += 1;
87
- beforeIndex += 1;
88
- } else {
89
- additions += 1;
90
- afterIndex += 1;
91
- }
92
- }
93
- removals += beforeLines.length - beforeIndex;
94
- additions += afterLines.length - afterIndex;
95
- return {
96
- additions,
97
- removals
98
- };
99
- };
100
- const getTargetURL = ({ documentID, proposal, req })=>{
101
- const adminRoute = req.payload.config.routes.admin || "/admin";
102
- if (proposal.action === "updateGlobal") {
103
- return `${adminRoute}/globals/${proposal.slug}`;
104
- }
105
- if (proposal.action === "delete") {
106
- return null;
107
- }
108
- const id = proposal.action === "create" ? documentID : proposal.id;
109
- if (!id) return null;
110
- return `${adminRoute}/collections/${proposal.collection}/${id}`;
111
- };
112
- const getUserID = (req)=>{
113
- const user = req.user;
114
- return user?.id === undefined ? undefined : String(user.id);
115
- };
116
- const logAIChange = async ({ changeLogCollection, target, proposal, req })=>{
117
- if (!changeLogCollection) return null;
118
- const before = redactSensitiveData(target.before);
119
- const after = redactSensitiveData(target.after);
120
- const { additions, removals } = countDiffChanges({
121
- after,
122
- before
123
- });
124
- const proposalForLog = {
125
- ...proposal
126
- };
127
- delete proposalForLog._aiSignature;
128
- try {
129
- await req.payload.create({
130
- collection: changeLogCollection,
131
- data: {
132
- action: proposal.action,
133
- additions,
134
- after,
135
- before,
136
- collection: "collection" in proposal ? proposal.collection : undefined,
137
- documentID: target.documentID === undefined ? undefined : String(target.documentID),
138
- proposal: redactSensitiveData(proposalForLog),
139
- removals,
140
- slug: "slug" in proposal ? proposal.slug : undefined,
141
- targetType: proposal.action === "updateGlobal" ? "global" : "collection",
142
- targetURL: getTargetURL({
143
- documentID: target.documentID,
144
- proposal,
145
- req
146
- }),
147
- title: proposal.label,
148
- userID: getUserID(req)
149
- },
150
- overrideAccess: true,
151
- req
152
- });
153
- return {
154
- action: proposal.action,
155
- additions,
156
- after,
157
- before,
158
- collection: "collection" in proposal ? proposal.collection : null,
159
- documentID: target.documentID === undefined ? null : String(target.documentID),
160
- removals,
161
- slug: "slug" in proposal ? proposal.slug : null,
162
- title: proposal.label,
163
- url: getTargetURL({
164
- documentID: target.documentID,
165
- proposal,
166
- req
167
- })
168
- };
169
- } catch (err) {
170
- req.payload.logger.error({
171
- err,
172
- msg: "AI change log entry could not be written",
173
- proposal: getProposalMeta(proposal)
174
- });
175
- return null;
176
- }
177
- };
178
- const isActionProposal = (proposal)=>{
179
- if (!isRecord(proposal) || typeof proposal.label !== "string") return false;
180
- if (proposal.action === "create") {
181
- return typeof proposal.collection === "string" && isRecord(proposal.data);
182
- }
183
- if (proposal.action === "update") {
184
- return typeof proposal.collection === "string" && typeof proposal.id === "string" && isRecord(proposal.data);
185
- }
186
- if (proposal.action === "delete") {
187
- return typeof proposal.collection === "string" && typeof proposal.id === "string";
188
- }
189
- if (proposal.action === "updateGlobal") {
190
- return typeof proposal.slug === "string" && isRecord(proposal.data);
191
- }
192
- return false;
193
- };
194
- export const createAIApplyActionEndpointHandler = (options = {})=>async (req)=>{
195
- if (!req.user) return Response.json({
196
- error: "Unauthorized"
197
- }, {
198
- status: 401
199
- });
200
- const body = req.json ? await req.json().catch(()=>null) : null;
201
- const proposal = body?.proposal;
202
- if (!proposal) return Response.json({
203
- error: "Proposal is required"
204
- }, {
205
- status: 400
206
- });
207
- if (!verifyAIActionProposal(proposal)) return Response.json({
208
- error: "Proposal signature is invalid or expired."
209
- }, {
210
- status: 400
211
- });
212
- if (!isActionProposal(proposal)) return Response.json({
213
- error: "Proposal is invalid."
214
- }, {
215
- status: 400
216
- });
217
- if ("data" in proposal && containsSensitiveData(proposal.data)) return Response.json({
218
- error: "Proposal contains sensitive fields and cannot be applied."
219
- }, {
220
- status: 400
221
- });
222
- let normalized;
223
- try {
224
- if (proposal.action === "updateGlobal") {
225
- if (!isKnownGlobal(req, proposal.slug)) return Response.json({
226
- error: "Unknown global"
227
- }, {
228
- status: 400
229
- });
230
- const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
231
- normalized = normalizeDataForFields(globalConfig?.fields || [], proposal.data);
232
- const before = await req.payload.findGlobal({
233
- depth: 2,
234
- req,
235
- slug: proposal.slug
236
- });
237
- const doc = await req.payload.updateGlobal({
238
- data: normalized.data,
239
- overrideAccess: false,
240
- req,
241
- slug: proposal.slug
242
- });
243
- const change = await logAIChange({
244
- changeLogCollection: options.changeLogCollection,
245
- proposal,
246
- req,
247
- target: {
248
- after: mergeData(before, normalized.data),
249
- before
250
- }
251
- });
252
- return Response.json({
253
- change,
254
- doc: getAppliedDocReference(doc),
255
- status: "applied"
256
- });
257
- }
258
- if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action)) return Response.json({
259
- error: "Unknown collection"
260
- }, {
261
- status: 400
262
- });
263
- if (proposal.action === "delete") {
264
- const doc = await req.payload.delete({
265
- collection: proposal.collection,
266
- id: proposal.id,
267
- overrideAccess: false,
268
- req
269
- });
270
- const change = await logAIChange({
271
- changeLogCollection: options.changeLogCollection,
272
- proposal,
273
- req,
274
- target: {
275
- after: {},
276
- before: doc,
277
- documentID: proposal.id
278
- }
279
- });
280
- return Response.json({
281
- change,
282
- doc: getAppliedDocReference(doc),
283
- status: "applied"
284
- });
285
- }
286
- const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
287
- normalized = normalizeAuthData(collectionConfig, normalizeDataForFields(getCollectionFields(collectionConfig), proposal.data));
288
- if (proposal.action === "create" && isAuthCollection(collectionConfig) && !normalized.data.password) {
289
- return Response.json({
290
- error: "Password is required when creating a user."
291
- }, {
292
- status: 400
293
- });
294
- }
295
- if (proposal.action === "create" && isAuthCollection(collectionConfig) && !normalized.data.email) {
296
- return Response.json({
297
- error: "Email is required when creating a user."
298
- }, {
299
- status: 400
300
- });
301
- }
302
- if (proposal.action === "create") {
303
- const doc = await req.payload.create({
304
- collection: proposal.collection,
305
- data: normalized.data,
306
- overrideAccess: false,
307
- req
308
- });
309
- const change = await logAIChange({
310
- changeLogCollection: options.changeLogCollection,
311
- proposal,
312
- req,
313
- target: {
314
- after: doc,
315
- before: {},
316
- documentID: doc.id
317
- }
318
- });
319
- return Response.json({
320
- change,
321
- doc: getAppliedDocReference(doc),
322
- status: "applied"
323
- });
324
- }
325
- const before = await req.payload.findByID({
326
- collection: proposal.collection,
327
- depth: 2,
328
- id: proposal.id,
329
- req
330
- });
331
- const doc = await req.payload.update({
332
- collection: proposal.collection,
333
- data: normalized.data,
334
- id: proposal.id,
335
- overrideAccess: false,
336
- req
337
- });
338
- const change = await logAIChange({
339
- changeLogCollection: options.changeLogCollection,
340
- proposal,
341
- req,
342
- target: {
343
- after: mergeData(before, normalized.data),
344
- before,
345
- documentID: proposal.id
346
- }
347
- });
348
- return Response.json({
349
- change,
350
- doc: getAppliedDocReference(doc),
351
- status: "applied"
352
- });
353
- } catch (err) {
354
- req.payload.logger.error({
355
- err,
356
- msg: "AI apply action failed",
357
- proposal: getProposalMeta(proposal)
358
- });
359
- return Response.json({
360
- error: "Could not apply proposal."
361
- }, {
362
- status: 400
363
- });
364
- }
365
- };
@@ -1,35 +0,0 @@
1
- import type { PayloadHandler } from "payload";
2
- import { type AIActionSignature } from "../ai/proposals.js";
3
- import { type AIModelConfig } from "../ai/providerOptions.js";
4
- import { type ResolvedAICollectionPermissionMap } from "../payload/collectionPermissions.js";
5
- export type AIActionProposal = ({
6
- action: "create";
7
- collection: string;
8
- data: Record<string, unknown>;
9
- label: string;
10
- } | {
11
- action: "delete";
12
- collection: string;
13
- id: string;
14
- label: string;
15
- } | {
16
- action: "update";
17
- collection: string;
18
- data: Record<string, unknown>;
19
- id: string;
20
- label: string;
21
- } | {
22
- action: "updateGlobal";
23
- data: Record<string, unknown>;
24
- label: string;
25
- slug: string;
26
- }) & {
27
- _aiSignature?: AIActionSignature;
28
- };
29
- type AIChatEndpointOptions = {
30
- allowUserApiKeys?: boolean;
31
- collections?: ResolvedAICollectionPermissionMap;
32
- models?: AIModelConfig;
33
- };
34
- export declare const createAIChatEndpointHandler: (options?: AIChatEndpointOptions) => PayloadHandler;
35
- export {};