@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,382 +0,0 @@
1
- import { streamText, stepCountIs } from "ai";
2
- import { z } from "zod";
3
- import { signAIActionProposal } from "../ai/proposals.js";
4
- import { getModel, getProviderConfig } from "../ai/providerRuntime.js";
5
- import { isAIProvider } from "../ai/providerOptions.js";
6
- import { containsSensitiveData } from "../ai/sensitiveData.js";
7
- import { buildPromptWithMentionContext, collectBlocks, describeCollectionLikeConfig, getAllowedCollectionSlugs, getMentionContext } from "../payload/schemaContext.js";
8
- import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
9
- const maxProposalLabelLength = 90;
10
- const getSafeProposalLabel = (label)=>{
11
- const firstLine = label.replace(/[*_`>#-]/g, "").split("\n").map((line)=>line.trim()).find(Boolean);
12
- if (!firstLine) return "Review proposed CMS change";
13
- if (firstLine.length <= maxProposalLabelLength) return firstLine;
14
- return `${firstLine.slice(0, maxProposalLabelLength - 3).trim()}...`;
15
- };
16
- export const createAIChatEndpointHandler = (options = {})=>async (req)=>{
17
- if (!req.user) return Response.json({
18
- error: "Unauthorized"
19
- }, {
20
- status: 401
21
- });
22
- const body = req.json ? await req.json().catch(()=>null) : null;
23
- const prompt = body?.prompt?.trim();
24
- if (!prompt) return Response.json({
25
- error: "Prompt is required"
26
- }, {
27
- status: 400
28
- });
29
- const user = req.user;
30
- const requestedProvider = body?.provider || user.aiProvider || "openai";
31
- if (!isAIProvider(requestedProvider)) return Response.json({
32
- error: `Unsupported AI provider: ${requestedProvider}`
33
- }, {
34
- status: 400
35
- });
36
- const provider = requestedProvider;
37
- const userApiKey = options.allowUserApiKeys === false ? null : user.aiApiKey;
38
- const providerConfig = getProviderConfig({
39
- apiKey: userApiKey,
40
- defaultModels: options.models?.defaults,
41
- model: body?.model,
42
- provider
43
- });
44
- const debug = {
45
- model: providerConfig.modelID,
46
- provider,
47
- tools: [
48
- "getDoc",
49
- "getGlobal",
50
- "listCollections",
51
- "listGlobals",
52
- "proposeCreateDoc",
53
- "proposeDeleteDoc",
54
- "proposeUpdateDoc",
55
- "proposeUpdateGlobal",
56
- "searchDocs"
57
- ]
58
- };
59
- if (!providerConfig.apiKey) {
60
- return Response.json({
61
- error: options.allowUserApiKeys === false ? `Configure a ${provider} API key in the server environment first.` : `Add a ${provider} API key to your account settings or configure it in the server environment first.`
62
- }, {
63
- status: 400
64
- });
65
- }
66
- try {
67
- const proposals = [];
68
- const addSignedProposal = (proposal)=>{
69
- if ("data" in proposal && containsSensitiveData(proposal.data)) {
70
- return {
71
- error: "Proposal contains sensitive fields and cannot be created."
72
- };
73
- }
74
- const signedProposal = signAIActionProposal(proposal);
75
- proposals.push(signedProposal);
76
- return signedProposal;
77
- };
78
- const collectionSlugs = getAllowedCollectionSlugs(req, options.collections);
79
- const globalSlugs = req.payload.config.globals?.map((global)=>global.slug) || [];
80
- const allowedCollections = req.payload.config.collections.filter((collection)=>collectionSlugs.includes(collection.slug));
81
- if (collectionSlugs.length === 0) {
82
- return Response.json({
83
- error: "No AI-enabled collections are configured."
84
- }, {
85
- status: 400
86
- });
87
- }
88
- const blockContexts = [
89
- ...allowedCollections.flatMap((collection)=>collectBlocks({
90
- fields: collection.fields,
91
- parent: collection.slug
92
- })),
93
- ...req.payload.config.globals?.flatMap((global)=>collectBlocks({
94
- fields: global.fields,
95
- parent: global.slug
96
- })) || []
97
- ];
98
- const mentionContext = await getMentionContext({
99
- blockContexts,
100
- collectionSlugs,
101
- collections: options.collections,
102
- globalSlugs,
103
- mentions: body?.mentions,
104
- req
105
- });
106
- const collectionSlugSchema = z.enum(collectionSlugs);
107
- const getDisallowedCollectionActionError = (collection, action)=>{
108
- if (isCollectionActionAllowed({
109
- action,
110
- permissions: options.collections,
111
- req,
112
- slug: collection
113
- })) return null;
114
- return {
115
- error: `${action} is not enabled for collection: ${collection}`
116
- };
117
- };
118
- const tools = {
119
- getDoc: {
120
- description: "Read one document by collection slug and document id.",
121
- inputSchema: z.object({
122
- collection: collectionSlugSchema,
123
- id: z.string().min(1)
124
- }),
125
- execute: async ({ collection, id })=>{
126
- return req.payload.findByID({
127
- collection: collection,
128
- depth: 2,
129
- id,
130
- overrideAccess: false,
131
- req
132
- });
133
- }
134
- },
135
- listCollections: {
136
- description: "List all Payload CMS collections available in this app.",
137
- inputSchema: z.object({}),
138
- execute: async ()=>{
139
- return allowedCollections.map((collection)=>describeCollectionLikeConfig({
140
- config: collection,
141
- permissions: options.collections,
142
- type: "collection"
143
- }));
144
- }
145
- },
146
- getGlobal: {
147
- description: "Read one Payload CMS global by slug.",
148
- inputSchema: z.object({
149
- slug: z.string().min(1)
150
- }),
151
- execute: async ({ slug })=>{
152
- const globalConfig = req.payload.config.globals?.find((global)=>global.slug === slug);
153
- if (!globalConfig) return {
154
- error: `Unknown global: ${slug}`
155
- };
156
- return req.payload.findGlobal({
157
- depth: 2,
158
- overrideAccess: false,
159
- req,
160
- slug: slug
161
- });
162
- }
163
- },
164
- listGlobals: {
165
- description: "List all Payload CMS globals available in this app.",
166
- inputSchema: z.object({}),
167
- execute: async ()=>{
168
- return req.payload.config.globals?.map((global)=>describeCollectionLikeConfig({
169
- config: global,
170
- type: "global"
171
- })) || [];
172
- }
173
- },
174
- proposeCreateDoc: {
175
- description: "Prepare a CMS document creation proposal. This does not write to the database. Use exact field names from listCollections. For array fields, provide arrays of objects matching their child fields. For richText fields, prefer plain text or omit if unsure.",
176
- inputSchema: z.object({
177
- collection: collectionSlugSchema,
178
- data: z.record(z.string(), z.unknown()),
179
- label: z.string().min(1)
180
- }),
181
- execute: async ({ collection, data, label })=>{
182
- const permissionError = getDisallowedCollectionActionError(collection, "create");
183
- if (permissionError) return permissionError;
184
- const proposal = {
185
- action: "create",
186
- collection,
187
- data,
188
- label: getSafeProposalLabel(label)
189
- };
190
- return addSignedProposal(proposal);
191
- }
192
- },
193
- proposeDeleteDoc: {
194
- description: "Prepare a CMS document deletion proposal. This does not write to the database.",
195
- inputSchema: z.object({
196
- collection: collectionSlugSchema,
197
- id: z.string().min(1),
198
- label: z.string().min(1)
199
- }),
200
- execute: async ({ collection, id, label })=>{
201
- const permissionError = getDisallowedCollectionActionError(collection, "delete");
202
- if (permissionError) return permissionError;
203
- const proposal = {
204
- action: "delete",
205
- collection,
206
- id,
207
- label: getSafeProposalLabel(label)
208
- };
209
- return addSignedProposal(proposal);
210
- }
211
- },
212
- proposeUpdateDoc: {
213
- description: "Prepare a CMS document update proposal. This does not write to the database. Use exact field names from listCollections. For array fields, provide arrays of objects matching their child fields. For richText fields, prefer plain text or omit if unsure.",
214
- inputSchema: z.object({
215
- collection: collectionSlugSchema,
216
- data: z.record(z.string(), z.unknown()),
217
- id: z.string().min(1),
218
- label: z.string().min(1)
219
- }),
220
- execute: async ({ collection, data, id, label })=>{
221
- const permissionError = getDisallowedCollectionActionError(collection, "update");
222
- if (permissionError) return permissionError;
223
- const proposal = {
224
- action: "update",
225
- collection,
226
- data,
227
- id,
228
- label: getSafeProposalLabel(label)
229
- };
230
- return addSignedProposal(proposal);
231
- }
232
- },
233
- proposeUpdateGlobal: {
234
- description: "Prepare a Payload global update proposal. This does not write to the database.",
235
- inputSchema: z.object({
236
- data: z.record(z.string(), z.unknown()),
237
- label: z.string().min(1),
238
- slug: z.string().min(1)
239
- }),
240
- execute: async ({ data, label, slug })=>{
241
- const globalConfig = req.payload.config.globals?.find((global)=>global.slug === slug);
242
- if (!globalConfig) return {
243
- error: `Unknown global: ${slug}`
244
- };
245
- const proposal = {
246
- action: "updateGlobal",
247
- data,
248
- label: getSafeProposalLabel(label),
249
- slug
250
- };
251
- return addSignedProposal(proposal);
252
- }
253
- },
254
- searchDocs: {
255
- description: "Search documents in one collection. Use query for a loose text search where possible.",
256
- inputSchema: z.object({
257
- collection: collectionSlugSchema,
258
- limit: z.number().int().min(1).max(10).default(5),
259
- query: z.string().optional()
260
- }),
261
- execute: async ({ collection, limit, query })=>{
262
- const collectionConfig = allowedCollections.find((item)=>item.slug === collection);
263
- const searchableFields = collectionConfig?.fields.filter((field)=>"name" in field && [
264
- "email",
265
- "text",
266
- "textarea"
267
- ].includes(field.type)).map((field)=>"name" in field ? field.name : null).filter(Boolean) || [];
268
- const where = query && searchableFields.length > 0 ? {
269
- or: searchableFields.map((field)=>({
270
- [field]: {
271
- contains: query
272
- }
273
- }))
274
- } : undefined;
275
- return req.payload.find({
276
- collection: collection,
277
- depth: 1,
278
- limit,
279
- overrideAccess: false,
280
- req,
281
- where
282
- });
283
- }
284
- }
285
- };
286
- const encoder = new TextEncoder();
287
- const sendEvent = (controller, event, data)=>{
288
- controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
289
- };
290
- const result = streamText({
291
- maxOutputTokens: 700,
292
- model: getModel({
293
- apiKey: providerConfig.apiKey,
294
- model: providerConfig.modelID,
295
- provider
296
- }),
297
- prompt: buildPromptWithMentionContext({
298
- mentionContext,
299
- prompt
300
- }),
301
- stopWhen: stepCountIs(6),
302
- system: [
303
- "You are a CMS assistant inside Payload CMS.",
304
- "Use tools to inspect CMS schema and content before answering content questions.",
305
- "When mention context is provided, use it as the selected CMS scope and prefer it over guessing collection names.",
306
- "Never claim that a write has been applied unless the user confirms an action proposal in the UI.",
307
- "For create, update, and delete requests, call the proposal tools instead of directly changing data.",
308
- "Keep the visible response under 40 words and describe only what kind of change was proposed.",
309
- "Write visible responses as plain text only. Do not use Markdown formatting, bold markers (**), headings, bullets, tables, or code fences.",
310
- "Do not include proposed field values, full replacement text, quoted content, markdown sections, code blocks, or headings like 'New content'/'Neuer Inhalt' in the visible response.",
311
- "Put all concrete field values and replacement content only in proposal tool data. Proposal labels must be short action summaries and must not contain proposed content."
312
- ].join("\n"),
313
- tools
314
- });
315
- const stream = new ReadableStream({
316
- start: async (controller)=>{
317
- let didSendTerminalEvent = false;
318
- try {
319
- for await (const part of result.fullStream){
320
- if (part.type === "text-delta") {
321
- sendEvent(controller, "text", {
322
- delta: part.text
323
- });
324
- continue;
325
- }
326
- if (part.type === "error") {
327
- didSendTerminalEvent = true;
328
- sendEvent(controller, "error", {
329
- error: "AI request failed."
330
- });
331
- break;
332
- }
333
- if (part.type === "finish") {
334
- sendEvent(controller, "proposals", {
335
- proposals
336
- });
337
- sendEvent(controller, "done", {});
338
- didSendTerminalEvent = true;
339
- }
340
- }
341
- if (!didSendTerminalEvent) {
342
- sendEvent(controller, "proposals", {
343
- proposals
344
- });
345
- sendEvent(controller, "done", {});
346
- }
347
- } catch (err) {
348
- req.payload.logger.error({
349
- debug,
350
- err,
351
- msg: "AI chat stream failed"
352
- });
353
- if (!didSendTerminalEvent) {
354
- sendEvent(controller, "error", {
355
- error: "AI request failed."
356
- });
357
- }
358
- } finally{
359
- controller.close();
360
- }
361
- }
362
- });
363
- return new Response(stream, {
364
- headers: {
365
- "Cache-Control": "no-cache, no-transform",
366
- Connection: "keep-alive",
367
- "Content-Type": "text/event-stream; charset=utf-8"
368
- }
369
- });
370
- } catch (err) {
371
- req.payload.logger.error({
372
- debug,
373
- err,
374
- msg: "AI chat request failed"
375
- });
376
- return Response.json({
377
- error: "AI request failed."
378
- }, {
379
- status: 500
380
- });
381
- }
382
- };
@@ -1,7 +0,0 @@
1
- import type { PayloadHandler } from "payload";
2
- import { type ResolvedAICollectionPermissionMap } from "../payload/collectionPermissions.js";
3
- type AIMentionSuggestionsEndpointOptions = {
4
- collections?: ResolvedAICollectionPermissionMap;
5
- };
6
- export declare const createAIMentionSuggestionsEndpointHandler: (options?: AIMentionSuggestionsEndpointOptions) => PayloadHandler;
7
- export {};
@@ -1,63 +0,0 @@
1
- import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
2
- const getDocLabel = (doc, useAsTitle)=>{
3
- const titleField = useAsTitle && typeof doc[useAsTitle] === "string" ? doc[useAsTitle] : null;
4
- return (titleField || doc.title || doc.name || doc.email || doc.id || "Untitled").toString();
5
- };
6
- export const createAIMentionSuggestionsEndpointHandler = (options = {})=>async (req)=>{
7
- if (!req.user) return Response.json({
8
- error: "Unauthorized"
9
- }, {
10
- status: 401
11
- });
12
- const body = req.json ? await req.json().catch(()=>null) : null;
13
- const query = body?.query?.trim();
14
- const collectionSlug = body?.collectionSlug?.trim();
15
- if (!query && !collectionSlug) return Response.json({
16
- suggestions: []
17
- });
18
- const suggestions = [];
19
- const collections = req.payload.config.collections.filter((collection)=>{
20
- if (!isCollectionActionAllowed({
21
- action: "read",
22
- permissions: options.collections,
23
- req,
24
- slug: collection.slug
25
- })) return false;
26
- if (collectionSlug) return collection.slug === collectionSlug;
27
- return true;
28
- });
29
- for (const collection of collections){
30
- const searchableFields = collection.fields.filter((field)=>"name" in field && [
31
- "email",
32
- "text",
33
- "textarea"
34
- ].includes(field.type)).map((field)=>"name" in field ? field.name : null).filter(Boolean);
35
- if (query && searchableFields.length === 0) continue;
36
- const result = await req.payload.find({
37
- collection: collection.slug,
38
- depth: 0,
39
- limit: 3,
40
- overrideAccess: false,
41
- req,
42
- where: query && searchableFields.length > 0 ? {
43
- or: searchableFields.map((field)=>({
44
- [field]: {
45
- contains: query
46
- }
47
- }))
48
- } : undefined
49
- });
50
- for (const doc of result.docs){
51
- suggestions.push({
52
- collection: collection.slug,
53
- id: doc.id?.toString(),
54
- label: getDocLabel(doc, collection.admin?.useAsTitle),
55
- slug: `${collection.slug}:${doc.id?.toString()}`,
56
- type: "doc"
57
- });
58
- }
59
- }
60
- return Response.json({
61
- suggestions: suggestions.slice(0, 5)
62
- });
63
- };
@@ -1,7 +0,0 @@
1
- import type { PayloadHandler } from "payload";
2
- import { type ResolvedAICollectionPermissionMap } from "../payload/collectionPermissions.js";
3
- type AIProposalDiffEndpointOptions = {
4
- collections?: ResolvedAICollectionPermissionMap;
5
- };
6
- export declare const createAIProposalDiffEndpointHandler: (options?: AIProposalDiffEndpointOptions) => PayloadHandler;
7
- export {};
@@ -1,138 +0,0 @@
1
- import { verifyAIActionProposal } from "../ai/proposals.js";
2
- import { redactSensitiveData } from "../ai/sensitiveData.js";
3
- import { getCollectionFields, normalizeAuthData, normalizeDataForFields } from "../payload/normalizeData.js";
4
- import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
5
- const isRecord = (value)=>{
6
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7
- };
8
- const isActionProposal = (proposal)=>{
9
- if (!isRecord(proposal) || typeof proposal.label !== "string") return false;
10
- if (proposal.action === "create") {
11
- return typeof proposal.collection === "string" && isRecord(proposal.data);
12
- }
13
- if (proposal.action === "update") {
14
- return typeof proposal.collection === "string" && typeof proposal.id === "string" && isRecord(proposal.data);
15
- }
16
- if (proposal.action === "delete") {
17
- return typeof proposal.collection === "string" && typeof proposal.id === "string";
18
- }
19
- if (proposal.action === "updateGlobal") {
20
- return typeof proposal.slug === "string" && isRecord(proposal.data);
21
- }
22
- return false;
23
- };
24
- const mergeData = (current, next)=>{
25
- return Object.fromEntries(Object.entries({
26
- ...current,
27
- ...next
28
- }).map(([key, value])=>{
29
- const currentValue = current[key];
30
- if (isRecord(currentValue) && isRecord(value)) {
31
- return [
32
- key,
33
- mergeData(currentValue, value)
34
- ];
35
- }
36
- return [
37
- key,
38
- value
39
- ];
40
- }));
41
- };
42
- export const createAIProposalDiffEndpointHandler = (options = {})=>async (req)=>{
43
- if (!req.user) return Response.json({
44
- error: "Unauthorized"
45
- }, {
46
- status: 401
47
- });
48
- const body = req.json ? await req.json().catch(()=>null) : null;
49
- const proposal = body?.proposal;
50
- if (!proposal) return Response.json({
51
- error: "Proposal is required"
52
- }, {
53
- status: 400
54
- });
55
- if (!verifyAIActionProposal(proposal)) return Response.json({
56
- error: "Proposal signature is invalid or expired."
57
- }, {
58
- status: 400
59
- });
60
- if (!isActionProposal(proposal)) return Response.json({
61
- error: "Proposal is invalid."
62
- }, {
63
- status: 400
64
- });
65
- try {
66
- if (proposal.action === "updateGlobal") {
67
- const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
68
- if (!globalConfig) return Response.json({
69
- error: "Unknown global"
70
- }, {
71
- status: 400
72
- });
73
- const normalized = normalizeDataForFields(globalConfig.fields || [], proposal.data);
74
- const doc = await req.payload.findGlobal({
75
- depth: 2,
76
- overrideAccess: false,
77
- req,
78
- slug: proposal.slug
79
- });
80
- return Response.json({
81
- after: redactSensitiveData(mergeData(doc, normalized.data)),
82
- before: redactSensitiveData(doc)
83
- });
84
- }
85
- if (!isCollectionActionAllowed({
86
- action: proposal.action === "delete" ? "delete" : "read",
87
- permissions: options.collections,
88
- req,
89
- slug: proposal.collection
90
- })) return Response.json({
91
- error: "Unknown collection"
92
- }, {
93
- status: 400
94
- });
95
- if (proposal.action === "delete") {
96
- const doc = await req.payload.findByID({
97
- collection: proposal.collection,
98
- depth: 2,
99
- id: proposal.id,
100
- overrideAccess: false,
101
- req
102
- });
103
- return Response.json({
104
- after: {},
105
- before: redactSensitiveData(doc)
106
- });
107
- }
108
- const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
109
- const normalized = normalizeAuthData(collectionConfig, normalizeDataForFields(getCollectionFields(collectionConfig), proposal.data));
110
- if (proposal.action === "create") {
111
- return Response.json({
112
- after: redactSensitiveData(normalized.data),
113
- before: {}
114
- });
115
- }
116
- const doc = await req.payload.findByID({
117
- collection: proposal.collection,
118
- depth: 2,
119
- id: proposal.id,
120
- overrideAccess: false,
121
- req
122
- });
123
- return Response.json({
124
- after: redactSensitiveData(mergeData(doc, normalized.data)),
125
- before: redactSensitiveData(doc)
126
- });
127
- } catch (err) {
128
- req.payload.logger.error({
129
- err,
130
- msg: "AI proposal diff failed"
131
- });
132
- return Response.json({
133
- error: "Could not load proposal diff."
134
- }, {
135
- status: 400
136
- });
137
- }
138
- };
@@ -1,6 +0,0 @@
1
- import type { PayloadHandler } from "payload";
2
- type AIRecentChangesEndpointOptions = {
3
- changeLogCollection: string;
4
- };
5
- export declare const createAIRecentChangesEndpointHandler: (options: AIRecentChangesEndpointOptions) => PayloadHandler;
6
- export {};