@mvriu5/payload-ai 0.5.9 → 1.1.1
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.
- package/README.md +62 -6
- package/dist/ai/{proposals.d.ts → proposalSigning.d.ts} +1 -1
- package/dist/ai/{proposals.js → proposalSigning.js} +6 -11
- package/dist/ai/providerOptions.d.ts +17 -1
- package/dist/ai/providerOptions.js +35 -7
- package/dist/ai/providerRuntime.d.ts +2 -2
- package/dist/ai/providerRuntime.js +50 -16
- package/dist/ai/sensitiveData.js +2 -6
- package/dist/components/AIInput.d.ts +2 -1
- package/dist/components/AIInput.js +363 -135
- package/dist/components/AIInput.module.css +15 -1
- package/dist/components/APIKeyField.d.ts +3 -0
- package/dist/components/APIKeyField.js +63 -0
- package/dist/components/ActionToast.d.ts +30 -0
- package/dist/components/ActionToast.js +263 -0
- package/dist/components/AuditLogList.d.ts +32 -0
- package/dist/components/AuditLogList.js +104 -0
- package/dist/components/{RecentChangesList.module.css → AuditLogList.module.css} +18 -1
- package/dist/components/DiffDialog.d.ts +10 -3
- package/dist/components/DiffDialog.js +284 -97
- package/dist/components/DiffDialog.module.css +86 -2
- package/dist/components/Icons.d.ts +5 -0
- package/dist/components/Icons.js +436 -214
- package/dist/components/MentionPopover.d.ts +19 -0
- package/dist/components/MentionPopover.js +95 -0
- package/dist/components/{CollectionMentionPopover.module.css → MentionPopover.module.css} +0 -2
- package/dist/components/hooks/useAISettings.d.ts +6 -4
- package/dist/components/hooks/useAISettings.js +18 -3
- package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +3 -3
- package/dist/components/hooks/useDocumentMentionSuggestions.js +2 -2
- package/dist/exports/client.d.ts +4 -2
- package/dist/exports/client.js +4 -2
- package/dist/handlers/applyActionHandler.d.ts +8 -0
- package/dist/handlers/applyActionHandler.js +563 -0
- package/dist/handlers/auditLogHandler.d.ts +6 -0
- package/dist/{endpoints/aiRecentChangesEndpointHandler.js → handlers/auditLogHandler.js} +12 -8
- package/dist/handlers/chatHandler.d.ts +42 -0
- package/dist/handlers/chatHandler.js +721 -0
- package/dist/handlers/mentionSuggestionHandler.d.ts +7 -0
- package/dist/handlers/mentionSuggestionHandler.js +97 -0
- package/dist/handlers/proposalDiffHandler.d.ts +7 -0
- package/dist/handlers/proposalDiffHandler.js +208 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +52 -20
- package/dist/payload/collectionPermissions.d.ts +14 -13
- package/dist/payload/collectionPermissions.js +3 -2
- package/dist/payload/normalizeData.d.ts +8 -0
- package/dist/payload/normalizeData.js +25 -3
- package/dist/payload/schemaContext.d.ts +43 -16
- package/dist/payload/schemaContext.js +139 -18
- package/dist/payload/shared.d.ts +33 -0
- package/dist/payload/shared.js +114 -0
- package/package.json +45 -9
- package/dist/components/AIActionProposalList.d.ts +0 -26
- package/dist/components/AIActionProposalList.js +0 -162
- package/dist/components/AIApiKeyField.d.ts +0 -2
- package/dist/components/AIApiKeyField.js +0 -47
- package/dist/components/CollectionMentionPopover.d.ts +0 -16
- package/dist/components/CollectionMentionPopover.js +0 -77
- package/dist/components/RecentChangesList.d.ts +0 -18
- package/dist/components/RecentChangesList.js +0 -66
- package/dist/components/hooks/utils.d.ts +0 -1
- package/dist/components/hooks/utils.js +0 -3
- package/dist/endpoints/aiApplyActionEndpointHandler.d.ts +0 -8
- package/dist/endpoints/aiApplyActionEndpointHandler.js +0 -365
- package/dist/endpoints/aiChatEndpointHandler.d.ts +0 -35
- package/dist/endpoints/aiChatEndpointHandler.js +0 -382
- package/dist/endpoints/aiMentionSuggestionsEndpointHandler.d.ts +0 -7
- package/dist/endpoints/aiMentionSuggestionsEndpointHandler.js +0 -63
- package/dist/endpoints/aiProposalDiffEndpointHandler.d.ts +0 -7
- package/dist/endpoints/aiProposalDiffEndpointHandler.js +0 -138
- package/dist/endpoints/aiRecentChangesEndpointHandler.d.ts +0 -6
- /package/dist/components/{AIActionProposalList.module.css → ActionToast.module.css} +0 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { verifyActionProposal } from "../ai/proposalSigning.js";
|
|
2
|
+
import { containsSensitiveData, redactSensitiveData } from "../ai/sensitiveData.js";
|
|
3
|
+
import { getCollectionFields, getLocalizedRequiredFallbackData, isAuthCollection, normalizeAuthData, normalizeDataForFields } from "../payload/normalizeData.js";
|
|
4
|
+
import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
|
|
5
|
+
import { getDefaultLocale, getJSONLineKey, getOptionalNumber, hasLocalizedData, isActionProposal, isKnownGlobal, mergeData } from "../payload/shared.js";
|
|
6
|
+
const getProposalMeta = (proposal)=>{
|
|
7
|
+
if (!proposal) return {};
|
|
8
|
+
return {
|
|
9
|
+
action: proposal.action,
|
|
10
|
+
collection: "collection" in proposal ? proposal.collection : undefined,
|
|
11
|
+
id: "id" in proposal ? proposal.id : undefined,
|
|
12
|
+
slug: "slug" in proposal ? proposal.slug : undefined
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
const getAppliedDocReference = (doc)=>{
|
|
16
|
+
return doc?.id === undefined ? undefined : {
|
|
17
|
+
id: doc.id
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
const isAllowedCollection = (req, collection, collections, action = "read")=>{
|
|
21
|
+
return isCollectionActionAllowed({
|
|
22
|
+
action,
|
|
23
|
+
permissions: collections,
|
|
24
|
+
req,
|
|
25
|
+
slug: collection
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const applyLocalizedRequiredFallback = ({ data, fallbackSource, fields })=>{
|
|
29
|
+
return mergeData(getLocalizedRequiredFallbackData({
|
|
30
|
+
fields,
|
|
31
|
+
source: fallbackSource,
|
|
32
|
+
target: data
|
|
33
|
+
}), data);
|
|
34
|
+
};
|
|
35
|
+
const countDiffChanges = ({ after, before })=>{
|
|
36
|
+
const beforeLines = JSON.stringify(before, null, 2).split("\n");
|
|
37
|
+
const afterLines = JSON.stringify(after, null, 2).split("\n");
|
|
38
|
+
const dp = Array.from({
|
|
39
|
+
length: beforeLines.length + 1
|
|
40
|
+
}, ()=>Array(afterLines.length + 1).fill(0));
|
|
41
|
+
let additions = 0;
|
|
42
|
+
let removals = 0;
|
|
43
|
+
for(let i = beforeLines.length - 1; i >= 0; i -= 1){
|
|
44
|
+
for(let j = afterLines.length - 1; j >= 0; j -= 1){
|
|
45
|
+
dp[i][j] = beforeLines[i] === afterLines[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let beforeIndex = 0;
|
|
49
|
+
let afterIndex = 0;
|
|
50
|
+
while(beforeIndex < beforeLines.length && afterIndex < afterLines.length){
|
|
51
|
+
if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
|
|
52
|
+
beforeIndex += 1;
|
|
53
|
+
afterIndex += 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const beforeKey = getJSONLineKey(beforeLines[beforeIndex]);
|
|
57
|
+
const afterKey = getJSONLineKey(afterLines[afterIndex]);
|
|
58
|
+
if (beforeKey && beforeKey === afterKey) {
|
|
59
|
+
removals += 1;
|
|
60
|
+
additions += 1;
|
|
61
|
+
beforeIndex += 1;
|
|
62
|
+
afterIndex += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (dp[beforeIndex + 1][afterIndex] >= dp[beforeIndex][afterIndex + 1]) {
|
|
66
|
+
removals += 1;
|
|
67
|
+
beforeIndex += 1;
|
|
68
|
+
} else {
|
|
69
|
+
additions += 1;
|
|
70
|
+
afterIndex += 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
removals += beforeLines.length - beforeIndex;
|
|
74
|
+
additions += afterLines.length - afterIndex;
|
|
75
|
+
return {
|
|
76
|
+
additions,
|
|
77
|
+
removals
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
const getTargetURL = ({ documentID, proposal, req })=>{
|
|
81
|
+
const adminRoute = req.payload.config.routes.admin || "/admin";
|
|
82
|
+
if (proposal.action === "updateGlobal") return `${adminRoute}/globals/${proposal.slug}`;
|
|
83
|
+
if (proposal.action === "delete") return null;
|
|
84
|
+
const id = proposal.action === "create" ? documentID : proposal.id;
|
|
85
|
+
if (!id) return null;
|
|
86
|
+
return `${adminRoute}/collections/${proposal.collection}/${id}`;
|
|
87
|
+
};
|
|
88
|
+
const getUserID = (req)=>{
|
|
89
|
+
const user = req.user;
|
|
90
|
+
return user?.id === undefined ? undefined : String(user.id);
|
|
91
|
+
};
|
|
92
|
+
const getUserLabel = (req)=>{
|
|
93
|
+
const user = req.user;
|
|
94
|
+
if (typeof user?.email === "string" && user.email) return user.email;
|
|
95
|
+
if (typeof user?.name === "string" && user.name) return user.name;
|
|
96
|
+
if (typeof user?.username === "string" && user.username) return user.username;
|
|
97
|
+
if (user?.id !== undefined) return String(user.id);
|
|
98
|
+
return null;
|
|
99
|
+
};
|
|
100
|
+
const logAIChange = async ({ changeLogCollection, context, target, proposal, req })=>{
|
|
101
|
+
if (!changeLogCollection) return null;
|
|
102
|
+
const before = redactSensitiveData(target.before);
|
|
103
|
+
const after = redactSensitiveData(target.after);
|
|
104
|
+
const { additions, removals } = countDiffChanges({
|
|
105
|
+
after,
|
|
106
|
+
before
|
|
107
|
+
});
|
|
108
|
+
const proposalForLog = {
|
|
109
|
+
...proposal
|
|
110
|
+
};
|
|
111
|
+
delete proposalForLog._aiSignature;
|
|
112
|
+
try {
|
|
113
|
+
await req.payload.create({
|
|
114
|
+
collection: changeLogCollection,
|
|
115
|
+
data: {
|
|
116
|
+
action: proposal.action,
|
|
117
|
+
additions,
|
|
118
|
+
after,
|
|
119
|
+
aiResponse: context?.aiResponse,
|
|
120
|
+
before,
|
|
121
|
+
collection: "collection" in proposal ? proposal.collection : undefined,
|
|
122
|
+
documentID: target.documentID === undefined ? undefined : String(target.documentID),
|
|
123
|
+
inputTokens: context?.tokenUsage?.inputTokens,
|
|
124
|
+
outputTokens: context?.tokenUsage?.outputTokens,
|
|
125
|
+
prompt: context?.prompt,
|
|
126
|
+
proposal: redactSensitiveData(proposalForLog),
|
|
127
|
+
removals,
|
|
128
|
+
slug: "slug" in proposal ? proposal.slug : undefined,
|
|
129
|
+
totalTokens: context?.tokenUsage?.totalTokens,
|
|
130
|
+
targetType: proposal.action === "updateGlobal" ? "global" : "collection",
|
|
131
|
+
targetURL: getTargetURL({
|
|
132
|
+
documentID: target.documentID,
|
|
133
|
+
proposal,
|
|
134
|
+
req
|
|
135
|
+
}),
|
|
136
|
+
title: proposal.label,
|
|
137
|
+
userID: getUserID(req),
|
|
138
|
+
userLabel: getUserLabel(req)
|
|
139
|
+
},
|
|
140
|
+
overrideAccess: true,
|
|
141
|
+
req
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
action: proposal.action,
|
|
145
|
+
additions,
|
|
146
|
+
after,
|
|
147
|
+
aiResponse: context?.aiResponse || null,
|
|
148
|
+
before,
|
|
149
|
+
collection: "collection" in proposal ? proposal.collection : null,
|
|
150
|
+
createdAt: new Date().toISOString(),
|
|
151
|
+
documentID: target.documentID === undefined ? null : String(target.documentID),
|
|
152
|
+
inputTokens: context?.tokenUsage?.inputTokens ?? null,
|
|
153
|
+
outputTokens: context?.tokenUsage?.outputTokens ?? null,
|
|
154
|
+
prompt: context?.prompt || null,
|
|
155
|
+
removals,
|
|
156
|
+
slug: "slug" in proposal ? proposal.slug : null,
|
|
157
|
+
totalTokens: context?.tokenUsage?.totalTokens ?? null,
|
|
158
|
+
targetType: proposal.action === "updateGlobal" ? "global" : "collection",
|
|
159
|
+
title: proposal.label,
|
|
160
|
+
userID: getUserID(req) || null,
|
|
161
|
+
userLabel: getUserLabel(req),
|
|
162
|
+
url: getTargetURL({
|
|
163
|
+
documentID: target.documentID,
|
|
164
|
+
proposal,
|
|
165
|
+
req
|
|
166
|
+
})
|
|
167
|
+
};
|
|
168
|
+
} catch (err) {
|
|
169
|
+
req.payload.logger.error({
|
|
170
|
+
err,
|
|
171
|
+
msg: "AI change log entry could not be written",
|
|
172
|
+
proposal: getProposalMeta(proposal)
|
|
173
|
+
});
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
178
|
+
if (!req.user) return Response.json({
|
|
179
|
+
error: "Unauthorized"
|
|
180
|
+
}, {
|
|
181
|
+
status: 401
|
|
182
|
+
});
|
|
183
|
+
const body = req.json ? await req.json().catch(()=>null) : null;
|
|
184
|
+
const proposal = body?.proposal;
|
|
185
|
+
if (!proposal) return Response.json({
|
|
186
|
+
error: "Proposal is required"
|
|
187
|
+
}, {
|
|
188
|
+
status: 400
|
|
189
|
+
});
|
|
190
|
+
if (!verifyActionProposal(proposal)) return Response.json({
|
|
191
|
+
error: "Proposal signature is invalid or expired."
|
|
192
|
+
}, {
|
|
193
|
+
status: 400
|
|
194
|
+
});
|
|
195
|
+
if (!isActionProposal(proposal)) return Response.json({
|
|
196
|
+
error: "Proposal is invalid."
|
|
197
|
+
}, {
|
|
198
|
+
status: 400
|
|
199
|
+
});
|
|
200
|
+
if ("data" in proposal && proposal.data && containsSensitiveData(proposal.data)) return Response.json({
|
|
201
|
+
error: "Proposal contains sensitive fields and cannot be applied."
|
|
202
|
+
}, {
|
|
203
|
+
status: 400
|
|
204
|
+
});
|
|
205
|
+
if (hasLocalizedData(proposal) && Object.values(proposal.localizedData).some((value)=>containsSensitiveData(value))) return Response.json({
|
|
206
|
+
error: "Proposal contains sensitive fields and cannot be applied."
|
|
207
|
+
}, {
|
|
208
|
+
status: 400
|
|
209
|
+
});
|
|
210
|
+
let normalized;
|
|
211
|
+
const logContext = {
|
|
212
|
+
aiResponse: typeof body?.aiResponse === "string" && body.aiResponse.trim() ? body.aiResponse.trim() : undefined,
|
|
213
|
+
prompt: typeof body?.prompt === "string" && body.prompt.trim() ? body.prompt.trim() : undefined,
|
|
214
|
+
tokenUsage: body?.tokenUsage ? {
|
|
215
|
+
inputTokens: getOptionalNumber(body.tokenUsage.inputTokens),
|
|
216
|
+
outputTokens: getOptionalNumber(body.tokenUsage.outputTokens),
|
|
217
|
+
totalTokens: getOptionalNumber(body.tokenUsage.totalTokens)
|
|
218
|
+
} : undefined
|
|
219
|
+
};
|
|
220
|
+
try {
|
|
221
|
+
const defaultLocale = getDefaultLocale(req);
|
|
222
|
+
if (proposal.action === "updateGlobal") {
|
|
223
|
+
if (!isKnownGlobal(req, proposal.slug)) return Response.json({
|
|
224
|
+
error: "Unknown global"
|
|
225
|
+
}, {
|
|
226
|
+
status: 400
|
|
227
|
+
});
|
|
228
|
+
const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
|
|
229
|
+
if (hasLocalizedData(proposal)) {
|
|
230
|
+
const beforeByLocale = {};
|
|
231
|
+
const afterByLocale = {};
|
|
232
|
+
const globalFields = globalConfig?.fields || [];
|
|
233
|
+
const defaultLocaleDoc = defaultLocale ? await req.payload.findGlobal({
|
|
234
|
+
depth: 2,
|
|
235
|
+
fallbackLocale: false,
|
|
236
|
+
locale: defaultLocale,
|
|
237
|
+
req,
|
|
238
|
+
slug: proposal.slug
|
|
239
|
+
}) : null;
|
|
240
|
+
for (const [locale, localeData] of Object.entries(proposal.localizedData)){
|
|
241
|
+
const localeNormalized = normalizeDataForFields(globalFields, localeData);
|
|
242
|
+
const before = await req.payload.findGlobal({
|
|
243
|
+
depth: 2,
|
|
244
|
+
fallbackLocale: false,
|
|
245
|
+
locale,
|
|
246
|
+
req,
|
|
247
|
+
slug: proposal.slug
|
|
248
|
+
});
|
|
249
|
+
const completedData = applyLocalizedRequiredFallback({
|
|
250
|
+
data: localeNormalized.data,
|
|
251
|
+
fallbackSource: locale === defaultLocale ? before : defaultLocaleDoc || before,
|
|
252
|
+
fields: globalFields
|
|
253
|
+
});
|
|
254
|
+
await req.payload.updateGlobal({
|
|
255
|
+
data: completedData,
|
|
256
|
+
locale,
|
|
257
|
+
overrideAccess: false,
|
|
258
|
+
req,
|
|
259
|
+
slug: proposal.slug
|
|
260
|
+
});
|
|
261
|
+
beforeByLocale[locale] = before;
|
|
262
|
+
afterByLocale[locale] = mergeData(before, completedData);
|
|
263
|
+
}
|
|
264
|
+
const change = await logAIChange({
|
|
265
|
+
changeLogCollection: options.changeLogCollection,
|
|
266
|
+
context: logContext,
|
|
267
|
+
proposal,
|
|
268
|
+
req,
|
|
269
|
+
target: {
|
|
270
|
+
after: afterByLocale,
|
|
271
|
+
before: beforeByLocale
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
return Response.json({
|
|
275
|
+
change,
|
|
276
|
+
doc: undefined,
|
|
277
|
+
status: "applied"
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
normalized = normalizeDataForFields(globalConfig?.fields || [], proposal.data);
|
|
281
|
+
const before = await req.payload.findGlobal({
|
|
282
|
+
depth: 2,
|
|
283
|
+
...proposal.locale ? {
|
|
284
|
+
locale: proposal.locale
|
|
285
|
+
} : {},
|
|
286
|
+
req,
|
|
287
|
+
slug: proposal.slug
|
|
288
|
+
});
|
|
289
|
+
const doc = await req.payload.updateGlobal({
|
|
290
|
+
data: normalized.data,
|
|
291
|
+
...proposal.locale ? {
|
|
292
|
+
locale: proposal.locale
|
|
293
|
+
} : {},
|
|
294
|
+
overrideAccess: false,
|
|
295
|
+
req,
|
|
296
|
+
slug: proposal.slug
|
|
297
|
+
});
|
|
298
|
+
const change = await logAIChange({
|
|
299
|
+
changeLogCollection: options.changeLogCollection,
|
|
300
|
+
context: logContext,
|
|
301
|
+
proposal,
|
|
302
|
+
req,
|
|
303
|
+
target: {
|
|
304
|
+
after: mergeData(before, normalized.data),
|
|
305
|
+
before
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
return Response.json({
|
|
309
|
+
change,
|
|
310
|
+
doc: getAppliedDocReference(doc),
|
|
311
|
+
status: "applied"
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action)) return Response.json({
|
|
315
|
+
error: "Unknown collection"
|
|
316
|
+
}, {
|
|
317
|
+
status: 400
|
|
318
|
+
});
|
|
319
|
+
if (proposal.action === "delete") {
|
|
320
|
+
const doc = await req.payload.delete({
|
|
321
|
+
collection: proposal.collection,
|
|
322
|
+
id: proposal.id,
|
|
323
|
+
overrideAccess: false,
|
|
324
|
+
req
|
|
325
|
+
});
|
|
326
|
+
const change = await logAIChange({
|
|
327
|
+
changeLogCollection: options.changeLogCollection,
|
|
328
|
+
context: logContext,
|
|
329
|
+
proposal,
|
|
330
|
+
req,
|
|
331
|
+
target: {
|
|
332
|
+
after: {},
|
|
333
|
+
before: doc,
|
|
334
|
+
documentID: proposal.id
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
return Response.json({
|
|
338
|
+
change,
|
|
339
|
+
doc: getAppliedDocReference(doc),
|
|
340
|
+
status: "applied"
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
|
|
344
|
+
const collectionFields = getCollectionFields(collectionConfig);
|
|
345
|
+
const normalizeCollectionData = (data)=>normalizeAuthData(collectionConfig, normalizeDataForFields(collectionFields, data));
|
|
346
|
+
if (hasLocalizedData(proposal)) {
|
|
347
|
+
if (proposal.action === "create") {
|
|
348
|
+
const localeEntries = Object.entries(proposal.localizedData);
|
|
349
|
+
const [firstLocale, firstLocaleData] = localeEntries[0] || [];
|
|
350
|
+
if (!firstLocale) return Response.json({
|
|
351
|
+
error: "Proposal is invalid."
|
|
352
|
+
}, {
|
|
353
|
+
status: 400
|
|
354
|
+
});
|
|
355
|
+
const firstNormalized = normalizeCollectionData(firstLocaleData);
|
|
356
|
+
if (isAuthCollection(collectionConfig) && !firstNormalized.data.password) {
|
|
357
|
+
return Response.json({
|
|
358
|
+
error: "Password is required when creating a user."
|
|
359
|
+
}, {
|
|
360
|
+
status: 400
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
if (isAuthCollection(collectionConfig) && !firstNormalized.data.email) {
|
|
364
|
+
return Response.json({
|
|
365
|
+
error: "Email is required when creating a user."
|
|
366
|
+
}, {
|
|
367
|
+
status: 400
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
const doc = await req.payload.create({
|
|
371
|
+
collection: proposal.collection,
|
|
372
|
+
data: firstNormalized.data,
|
|
373
|
+
locale: firstLocale,
|
|
374
|
+
overrideAccess: false,
|
|
375
|
+
req
|
|
376
|
+
});
|
|
377
|
+
const beforeByLocale = {
|
|
378
|
+
[firstLocale]: {}
|
|
379
|
+
};
|
|
380
|
+
const afterByLocale = {
|
|
381
|
+
[firstLocale]: doc
|
|
382
|
+
};
|
|
383
|
+
let fallbackSource = doc;
|
|
384
|
+
for (const [locale, localeData] of localeEntries.slice(1)){
|
|
385
|
+
const localeNormalized = normalizeCollectionData(localeData);
|
|
386
|
+
const completedData = applyLocalizedRequiredFallback({
|
|
387
|
+
data: localeNormalized.data,
|
|
388
|
+
fallbackSource,
|
|
389
|
+
fields: collectionFields
|
|
390
|
+
});
|
|
391
|
+
await req.payload.update({
|
|
392
|
+
collection: proposal.collection,
|
|
393
|
+
data: completedData,
|
|
394
|
+
id: String(doc.id),
|
|
395
|
+
locale,
|
|
396
|
+
overrideAccess: false,
|
|
397
|
+
req
|
|
398
|
+
});
|
|
399
|
+
beforeByLocale[locale] = {};
|
|
400
|
+
afterByLocale[locale] = completedData;
|
|
401
|
+
}
|
|
402
|
+
const change = await logAIChange({
|
|
403
|
+
changeLogCollection: options.changeLogCollection,
|
|
404
|
+
context: logContext,
|
|
405
|
+
proposal,
|
|
406
|
+
req,
|
|
407
|
+
target: {
|
|
408
|
+
after: afterByLocale,
|
|
409
|
+
before: beforeByLocale,
|
|
410
|
+
documentID: doc.id
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
return Response.json({
|
|
414
|
+
change,
|
|
415
|
+
doc: getAppliedDocReference(doc),
|
|
416
|
+
status: "applied"
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
const beforeByLocale = {};
|
|
420
|
+
const afterByLocale = {};
|
|
421
|
+
const defaultLocaleDoc = defaultLocale ? await req.payload.findByID({
|
|
422
|
+
collection: proposal.collection,
|
|
423
|
+
depth: 2,
|
|
424
|
+
fallbackLocale: false,
|
|
425
|
+
id: proposal.id,
|
|
426
|
+
locale: defaultLocale,
|
|
427
|
+
req
|
|
428
|
+
}) : null;
|
|
429
|
+
for (const [locale, localeData] of Object.entries(proposal.localizedData)){
|
|
430
|
+
const localeNormalized = normalizeCollectionData(localeData);
|
|
431
|
+
const before = await req.payload.findByID({
|
|
432
|
+
collection: proposal.collection,
|
|
433
|
+
depth: 2,
|
|
434
|
+
fallbackLocale: false,
|
|
435
|
+
id: proposal.id,
|
|
436
|
+
locale,
|
|
437
|
+
req
|
|
438
|
+
});
|
|
439
|
+
const completedData = applyLocalizedRequiredFallback({
|
|
440
|
+
data: localeNormalized.data,
|
|
441
|
+
fallbackSource: locale === defaultLocale ? before : defaultLocaleDoc || before,
|
|
442
|
+
fields: collectionFields
|
|
443
|
+
});
|
|
444
|
+
await req.payload.update({
|
|
445
|
+
collection: proposal.collection,
|
|
446
|
+
data: completedData,
|
|
447
|
+
id: proposal.id,
|
|
448
|
+
locale,
|
|
449
|
+
overrideAccess: false,
|
|
450
|
+
req
|
|
451
|
+
});
|
|
452
|
+
beforeByLocale[locale] = before;
|
|
453
|
+
afterByLocale[locale] = mergeData(before, completedData);
|
|
454
|
+
}
|
|
455
|
+
const change = await logAIChange({
|
|
456
|
+
changeLogCollection: options.changeLogCollection,
|
|
457
|
+
context: logContext,
|
|
458
|
+
proposal,
|
|
459
|
+
req,
|
|
460
|
+
target: {
|
|
461
|
+
after: afterByLocale,
|
|
462
|
+
before: beforeByLocale,
|
|
463
|
+
documentID: proposal.id
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
return Response.json({
|
|
467
|
+
change,
|
|
468
|
+
doc: {
|
|
469
|
+
id: proposal.id
|
|
470
|
+
},
|
|
471
|
+
status: "applied"
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
normalized = normalizeCollectionData(proposal.data);
|
|
475
|
+
if (proposal.action === "create" && isAuthCollection(collectionConfig) && !normalized.data.password) {
|
|
476
|
+
return Response.json({
|
|
477
|
+
error: "Password is required when creating a user."
|
|
478
|
+
}, {
|
|
479
|
+
status: 400
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
if (proposal.action === "create" && isAuthCollection(collectionConfig) && !normalized.data.email) {
|
|
483
|
+
return Response.json({
|
|
484
|
+
error: "Email is required when creating a user."
|
|
485
|
+
}, {
|
|
486
|
+
status: 400
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
if (proposal.action === "create") {
|
|
490
|
+
const doc = await req.payload.create({
|
|
491
|
+
collection: proposal.collection,
|
|
492
|
+
data: normalized.data,
|
|
493
|
+
...proposal.locale ? {
|
|
494
|
+
locale: proposal.locale
|
|
495
|
+
} : {},
|
|
496
|
+
overrideAccess: false,
|
|
497
|
+
req
|
|
498
|
+
});
|
|
499
|
+
const change = await logAIChange({
|
|
500
|
+
changeLogCollection: options.changeLogCollection,
|
|
501
|
+
context: logContext,
|
|
502
|
+
proposal,
|
|
503
|
+
req,
|
|
504
|
+
target: {
|
|
505
|
+
after: doc,
|
|
506
|
+
before: {},
|
|
507
|
+
documentID: doc.id
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
return Response.json({
|
|
511
|
+
change,
|
|
512
|
+
doc: getAppliedDocReference(doc),
|
|
513
|
+
status: "applied"
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
const before = await req.payload.findByID({
|
|
517
|
+
collection: proposal.collection,
|
|
518
|
+
depth: 2,
|
|
519
|
+
id: proposal.id,
|
|
520
|
+
...proposal.locale ? {
|
|
521
|
+
locale: proposal.locale
|
|
522
|
+
} : {},
|
|
523
|
+
req
|
|
524
|
+
});
|
|
525
|
+
const doc = await req.payload.update({
|
|
526
|
+
collection: proposal.collection,
|
|
527
|
+
data: normalized.data,
|
|
528
|
+
id: proposal.id,
|
|
529
|
+
...proposal.locale ? {
|
|
530
|
+
locale: proposal.locale
|
|
531
|
+
} : {},
|
|
532
|
+
overrideAccess: false,
|
|
533
|
+
req
|
|
534
|
+
});
|
|
535
|
+
const change = await logAIChange({
|
|
536
|
+
changeLogCollection: options.changeLogCollection,
|
|
537
|
+
context: logContext,
|
|
538
|
+
proposal,
|
|
539
|
+
req,
|
|
540
|
+
target: {
|
|
541
|
+
after: mergeData(before, normalized.data),
|
|
542
|
+
before,
|
|
543
|
+
documentID: proposal.id
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
return Response.json({
|
|
547
|
+
change,
|
|
548
|
+
doc: getAppliedDocReference(doc),
|
|
549
|
+
status: "applied"
|
|
550
|
+
});
|
|
551
|
+
} catch (err) {
|
|
552
|
+
req.payload.logger.error({
|
|
553
|
+
err,
|
|
554
|
+
msg: "AI apply action failed",
|
|
555
|
+
proposal: getProposalMeta(proposal)
|
|
556
|
+
});
|
|
557
|
+
return Response.json({
|
|
558
|
+
error: "Could not apply proposal."
|
|
559
|
+
}, {
|
|
560
|
+
status: 400
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
};
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
};
|
|
4
|
-
const getString = (value)=>{
|
|
5
|
-
return typeof value === "string" ? value : null;
|
|
6
|
-
};
|
|
7
|
-
export const createAIRecentChangesEndpointHandler = (options)=>async (req)=>{
|
|
1
|
+
import { getNumber, getString } from "../payload/shared.js";
|
|
2
|
+
export const createAuditLogHandler = (options)=>async (req)=>{
|
|
8
3
|
if (!req.user) return Response.json({
|
|
9
4
|
error: "Unauthorized"
|
|
10
5
|
}, {
|
|
@@ -13,7 +8,7 @@ export const createAIRecentChangesEndpointHandler = (options)=>async (req)=>{
|
|
|
13
8
|
const result = await req.payload.find({
|
|
14
9
|
collection: options.changeLogCollection,
|
|
15
10
|
depth: 0,
|
|
16
|
-
limit:
|
|
11
|
+
limit: 10,
|
|
17
12
|
overrideAccess: false,
|
|
18
13
|
req,
|
|
19
14
|
sort: "-createdAt"
|
|
@@ -23,12 +18,21 @@ export const createAIRecentChangesEndpointHandler = (options)=>async (req)=>{
|
|
|
23
18
|
action: getString(doc.action),
|
|
24
19
|
additions: getNumber(doc.additions),
|
|
25
20
|
after: doc.after,
|
|
21
|
+
aiResponse: getString(doc.aiResponse),
|
|
26
22
|
before: doc.before,
|
|
27
23
|
collection: getString(doc.collection),
|
|
24
|
+
createdAt: getString(doc.createdAt),
|
|
28
25
|
documentID: getString(doc.documentID),
|
|
26
|
+
inputTokens: getNumber(doc.inputTokens),
|
|
27
|
+
outputTokens: getNumber(doc.outputTokens),
|
|
28
|
+
prompt: getString(doc.prompt),
|
|
29
29
|
removals: getNumber(doc.removals),
|
|
30
30
|
slug: getString(doc.slug),
|
|
31
|
+
targetType: getString(doc.targetType),
|
|
32
|
+
totalTokens: getNumber(doc.totalTokens),
|
|
31
33
|
title: getString(doc.title) || "AI change",
|
|
34
|
+
userID: getString(doc.userID),
|
|
35
|
+
userLabel: getString(doc.userLabel),
|
|
32
36
|
url: getString(doc.targetURL)
|
|
33
37
|
}))
|
|
34
38
|
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PayloadHandler } from "payload";
|
|
2
|
+
import { type AIActionSignature } from "../ai/proposalSigning.js";
|
|
3
|
+
import { type AIModelConfig } from "../ai/providerOptions.js";
|
|
4
|
+
import { type ResolvedCollectionPermissionMap } from "../payload/collectionPermissions.js";
|
|
5
|
+
type LocalizedDataInput = Record<string, Record<string, unknown>>;
|
|
6
|
+
type ProposalWritePayload = {
|
|
7
|
+
data: Record<string, unknown>;
|
|
8
|
+
localizedData?: never;
|
|
9
|
+
} | {
|
|
10
|
+
data?: never;
|
|
11
|
+
localizedData: LocalizedDataInput;
|
|
12
|
+
};
|
|
13
|
+
export type ActionProposal = (({
|
|
14
|
+
action: "create";
|
|
15
|
+
collection: string;
|
|
16
|
+
label: string;
|
|
17
|
+
} & ProposalWritePayload) | {
|
|
18
|
+
action: "delete";
|
|
19
|
+
collection: string;
|
|
20
|
+
id: string;
|
|
21
|
+
label: string;
|
|
22
|
+
} | ({
|
|
23
|
+
action: "update";
|
|
24
|
+
collection: string;
|
|
25
|
+
id: string;
|
|
26
|
+
label: string;
|
|
27
|
+
} & ProposalWritePayload) | ({
|
|
28
|
+
action: "updateGlobal";
|
|
29
|
+
label: string;
|
|
30
|
+
slug: string;
|
|
31
|
+
} & ProposalWritePayload)) & {
|
|
32
|
+
_aiSignature?: AIActionSignature;
|
|
33
|
+
locale?: string;
|
|
34
|
+
};
|
|
35
|
+
type ChatOptions = {
|
|
36
|
+
allowUserApiKeys?: boolean;
|
|
37
|
+
collections?: ResolvedCollectionPermissionMap;
|
|
38
|
+
maxOutputTokens?: number;
|
|
39
|
+
models?: AIModelConfig;
|
|
40
|
+
};
|
|
41
|
+
export declare const createChatHandler: (options?: ChatOptions) => PayloadHandler;
|
|
42
|
+
export {};
|