@mvriu5/payload-ai 1.1.1 → 1.3.2
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 +42 -0
- package/dist/components/Icons.d.ts +0 -8
- package/dist/components/Icons.js +4 -216
- package/dist/components/{ActionToast.d.ts → action-toast/ActionToast.d.ts} +2 -2
- package/dist/components/{ActionToast.js → action-toast/ActionToast.js} +27 -35
- package/dist/components/{ActionToast.module.css → action-toast/ActionToast.module.css} +0 -79
- package/dist/components/ai-input/AIInput.js +433 -0
- package/dist/components/{AIInput.module.css → ai-input/AIInput.module.css} +119 -42
- package/dist/components/ai-input/badge.d.ts +14 -0
- package/dist/components/ai-input/badge.js +85 -0
- package/dist/components/{AuditLogList.d.ts → audit-log-list/AuditLogList.d.ts} +4 -8
- package/dist/components/{AuditLogList.js → audit-log-list/AuditLogList.js} +49 -21
- package/dist/components/{AuditLogList.module.css → audit-log-list/AuditLogList.module.css} +11 -65
- package/dist/components/dashboard/Dashboard.d.ts +2 -0
- package/dist/components/dashboard/Dashboard.js +15 -0
- package/dist/components/dashboard/Dashboard.module.css +13 -0
- package/dist/components/{DiffDialog.d.ts → diff-dialog/DiffDialog.d.ts} +2 -2
- package/dist/components/{DiffDialog.js → diff-dialog/DiffDialog.js} +200 -189
- package/dist/components/{DiffDialog.module.css → diff-dialog/DiffDialog.module.css} +17 -35
- package/dist/components/hooks/useAIChatStream.d.ts +39 -0
- package/dist/components/hooks/useAIChatStream.js +217 -0
- package/dist/components/hooks/useAISettings.js +26 -25
- package/dist/components/hooks/useAuditLog.d.ts +11 -0
- package/dist/components/hooks/useAuditLog.js +41 -0
- package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +1 -1
- package/dist/components/hooks/useDocumentMentionSuggestions.js +31 -27
- package/dist/components/hooks/useMentions.d.ts +58 -0
- package/dist/components/hooks/useMentions.js +276 -0
- package/dist/components/hooks/usePluginConfig.d.ts +52 -0
- package/dist/components/hooks/usePluginConfig.js +20 -0
- package/dist/components/{MentionPopover.d.ts → mention-popover/MentionPopover.d.ts} +2 -4
- package/dist/components/{MentionPopover.js → mention-popover/MentionPopover.js} +1 -8
- package/dist/components/{MentionPopover.module.css → mention-popover/MentionPopover.module.css} +1 -1
- package/dist/exports/client.d.ts +2 -2
- package/dist/exports/client.js +2 -2
- package/dist/handlers/applyActionHandler.js +341 -112
- package/dist/handlers/chatHandler.js +940 -84
- package/dist/handlers/mediaUploadHandler.d.ts +7 -0
- package/dist/handlers/mediaUploadHandler.js +99 -0
- package/dist/handlers/mentionSuggestionHandler.js +16 -14
- package/dist/handlers/proposalDiffHandler.js +117 -50
- package/dist/index.d.ts +6 -0
- package/dist/index.js +39 -7
- package/dist/payload/collectionPermissions.js +3 -1
- package/dist/payload/logging.d.ts +9 -0
- package/dist/payload/logging.js +14 -0
- package/dist/payload/normalizeData.d.ts +16 -12
- package/dist/payload/normalizeData.js +47 -14
- package/dist/payload/proposalData.d.ts +31 -0
- package/dist/payload/proposalData.js +578 -0
- package/dist/payload/schemaContext.d.ts +3 -7
- package/dist/payload/schemaContext.js +147 -66
- package/package.json +6 -6
- package/dist/components/AIInput.js +0 -747
- /package/dist/components/{AIInput.d.ts → ai-input/AIInput.d.ts} +0 -0
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { verifyActionProposal } from "../ai/proposalSigning.js";
|
|
2
2
|
import { containsSensitiveData, redactSensitiveData } from "../ai/sensitiveData.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getSchemaFields } from "../payload/normalizeData.js";
|
|
4
|
+
import { applyLocalizedRequiredFallbackToPreparedData, prepareProposalWriteData } from "../payload/proposalData.js";
|
|
4
5
|
import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
|
|
5
|
-
import { getDefaultLocale, getJSONLineKey, getOptionalNumber, hasLocalizedData, isActionProposal, isKnownGlobal, mergeData } from "../payload/shared.js";
|
|
6
|
+
import { getDefaultLocale, getJSONLineKey, getOptionalNumber, hasLocalizedData, isActionProposal, isKnownGlobal, isRecord, mergeData } from "../payload/shared.js";
|
|
7
|
+
import { getLogPreview, logHandlerEvent } from "../payload/logging.js";
|
|
8
|
+
const getProposalLogSummary = (proposal)=>({
|
|
9
|
+
action: proposal.action,
|
|
10
|
+
collection: "collection" in proposal ? proposal.collection : undefined,
|
|
11
|
+
hasData: "data" in proposal && Boolean(proposal.data),
|
|
12
|
+
hasLocalizedData: "localizedData" in proposal && Boolean(proposal.localizedData),
|
|
13
|
+
id: "id" in proposal ? proposal.id : undefined,
|
|
14
|
+
label: proposal.label,
|
|
15
|
+
locale: proposal.locale,
|
|
16
|
+
locales: "localizedData" in proposal && proposal.localizedData ? Object.keys(proposal.localizedData) : undefined,
|
|
17
|
+
slug: "slug" in proposal ? proposal.slug : undefined
|
|
18
|
+
});
|
|
6
19
|
const getProposalMeta = (proposal)=>{
|
|
7
20
|
if (!proposal) return {};
|
|
8
21
|
return {
|
|
@@ -12,6 +25,12 @@ const getProposalMeta = (proposal)=>{
|
|
|
12
25
|
slug: "slug" in proposal ? proposal.slug : undefined
|
|
13
26
|
};
|
|
14
27
|
};
|
|
28
|
+
const createApplyDebugPayload = ({ details, phase, proposal, reason })=>({
|
|
29
|
+
...getProposalMeta(proposal),
|
|
30
|
+
details,
|
|
31
|
+
phase,
|
|
32
|
+
reason
|
|
33
|
+
});
|
|
15
34
|
const getAppliedDocReference = (doc)=>{
|
|
16
35
|
return doc?.id === undefined ? undefined : {
|
|
17
36
|
id: doc.id
|
|
@@ -25,13 +44,6 @@ const isAllowedCollection = (req, collection, collections, action = "read")=>{
|
|
|
25
44
|
slug: collection
|
|
26
45
|
});
|
|
27
46
|
};
|
|
28
|
-
const applyLocalizedRequiredFallback = ({ data, fallbackSource, fields })=>{
|
|
29
|
-
return mergeData(getLocalizedRequiredFallbackData({
|
|
30
|
-
fields,
|
|
31
|
-
source: fallbackSource,
|
|
32
|
-
target: data
|
|
33
|
-
}), data);
|
|
34
|
-
};
|
|
35
47
|
const countDiffChanges = ({ after, before })=>{
|
|
36
48
|
const beforeLines = JSON.stringify(before, null, 2).split("\n");
|
|
37
49
|
const afterLines = JSON.stringify(after, null, 2).split("\n");
|
|
@@ -175,39 +187,90 @@ const logAIChange = async ({ changeLogCollection, context, target, proposal, req
|
|
|
175
187
|
}
|
|
176
188
|
};
|
|
177
189
|
export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
190
|
+
const failApply = ({ debug, error, logMessage, proposal, status = 400 })=>{
|
|
191
|
+
logHandlerEvent(req, "warn", {
|
|
192
|
+
debug,
|
|
193
|
+
msg: logMessage,
|
|
194
|
+
proposal: getProposalMeta(proposal)
|
|
195
|
+
});
|
|
196
|
+
return Response.json({
|
|
197
|
+
debug,
|
|
198
|
+
error
|
|
199
|
+
}, {
|
|
200
|
+
status
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
if (!req.user) {
|
|
204
|
+
return failApply({
|
|
205
|
+
debug: createApplyDebugPayload({
|
|
206
|
+
phase: "authorization",
|
|
207
|
+
reason: "unauthorized"
|
|
208
|
+
}),
|
|
209
|
+
error: "Unauthorized",
|
|
210
|
+
logMessage: "AI apply blocked: unauthorized request",
|
|
211
|
+
status: 401
|
|
212
|
+
});
|
|
213
|
+
}
|
|
183
214
|
const body = req.json ? await req.json().catch(()=>null) : null;
|
|
184
215
|
const proposal = body?.proposal;
|
|
185
|
-
if (!proposal)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
if (!
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
if (!proposal) {
|
|
217
|
+
return failApply({
|
|
218
|
+
debug: createApplyDebugPayload({
|
|
219
|
+
phase: "apply_validation",
|
|
220
|
+
reason: "missing_proposal"
|
|
221
|
+
}),
|
|
222
|
+
error: "Proposal is required",
|
|
223
|
+
logMessage: "AI apply blocked: missing proposal payload"
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (!verifyActionProposal(proposal)) {
|
|
227
|
+
return failApply({
|
|
228
|
+
debug: createApplyDebugPayload({
|
|
229
|
+
phase: "apply_validation",
|
|
230
|
+
proposal,
|
|
231
|
+
reason: "invalid_signature"
|
|
232
|
+
}),
|
|
233
|
+
error: "Proposal signature is invalid or expired.",
|
|
234
|
+
logMessage: "AI apply blocked: invalid proposal signature",
|
|
235
|
+
proposal
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (!isActionProposal(proposal)) {
|
|
239
|
+
return failApply({
|
|
240
|
+
debug: createApplyDebugPayload({
|
|
241
|
+
phase: "apply_validation",
|
|
242
|
+
proposal,
|
|
243
|
+
reason: "invalid_proposal_shape"
|
|
244
|
+
}),
|
|
245
|
+
error: "Proposal is invalid.",
|
|
246
|
+
logMessage: "AI apply blocked: invalid proposal shape",
|
|
247
|
+
proposal
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if ("data" in proposal && proposal.data && containsSensitiveData(proposal.data)) {
|
|
251
|
+
return failApply({
|
|
252
|
+
debug: createApplyDebugPayload({
|
|
253
|
+
phase: "apply_validation",
|
|
254
|
+
proposal,
|
|
255
|
+
reason: "sensitive_data_in_data"
|
|
256
|
+
}),
|
|
257
|
+
error: "Proposal contains sensitive fields and cannot be applied.",
|
|
258
|
+
logMessage: "AI apply blocked: sensitive data detected in proposal data",
|
|
259
|
+
proposal
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (hasLocalizedData(proposal) && Object.values(proposal.localizedData).some((value)=>containsSensitiveData(value))) {
|
|
263
|
+
return failApply({
|
|
264
|
+
debug: createApplyDebugPayload({
|
|
265
|
+
phase: "apply_validation",
|
|
266
|
+
proposal,
|
|
267
|
+
reason: "sensitive_data_in_localized_data"
|
|
268
|
+
}),
|
|
269
|
+
error: "Proposal contains sensitive fields and cannot be applied.",
|
|
270
|
+
logMessage: "AI apply blocked: sensitive data detected in localized proposal data",
|
|
271
|
+
proposal
|
|
272
|
+
});
|
|
273
|
+
}
|
|
211
274
|
const logContext = {
|
|
212
275
|
aiResponse: typeof body?.aiResponse === "string" && body.aiResponse.trim() ? body.aiResponse.trim() : undefined,
|
|
213
276
|
prompt: typeof body?.prompt === "string" && body.prompt.trim() ? body.prompt.trim() : undefined,
|
|
@@ -217,19 +280,61 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
217
280
|
totalTokens: getOptionalNumber(body.tokenUsage.totalTokens)
|
|
218
281
|
} : undefined
|
|
219
282
|
};
|
|
283
|
+
logHandlerEvent(req, "info", {
|
|
284
|
+
msg: "AI apply started",
|
|
285
|
+
promptPreview: getLogPreview(logContext.prompt),
|
|
286
|
+
proposal: getProposalLogSummary(proposal),
|
|
287
|
+
tokenUsage: logContext.tokenUsage
|
|
288
|
+
});
|
|
220
289
|
try {
|
|
290
|
+
const inferenceText = logContext.prompt;
|
|
221
291
|
const defaultLocale = getDefaultLocale(req);
|
|
222
292
|
if (proposal.action === "updateGlobal") {
|
|
223
|
-
if (!isKnownGlobal(req, proposal.slug))
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
293
|
+
if (!isKnownGlobal(req, proposal.slug)) {
|
|
294
|
+
return failApply({
|
|
295
|
+
debug: createApplyDebugPayload({
|
|
296
|
+
phase: "apply_validation",
|
|
297
|
+
proposal,
|
|
298
|
+
reason: "unknown_global"
|
|
299
|
+
}),
|
|
300
|
+
error: "Unknown global",
|
|
301
|
+
logMessage: "AI apply blocked: unknown global",
|
|
302
|
+
proposal
|
|
303
|
+
});
|
|
304
|
+
}
|
|
228
305
|
const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
|
|
229
306
|
if (hasLocalizedData(proposal)) {
|
|
230
307
|
const beforeByLocale = {};
|
|
231
308
|
const afterByLocale = {};
|
|
232
|
-
const globalFields =
|
|
309
|
+
const globalFields = getSchemaFields({
|
|
310
|
+
fields: globalConfig?.fields || [],
|
|
311
|
+
slug: proposal.slug
|
|
312
|
+
});
|
|
313
|
+
const preparedData = prepareProposalWriteData({
|
|
314
|
+
collectionConfig: {
|
|
315
|
+
fields: globalConfig?.fields || [],
|
|
316
|
+
slug: proposal.slug
|
|
317
|
+
},
|
|
318
|
+
inferenceText,
|
|
319
|
+
label: proposal.label,
|
|
320
|
+
localizedData: proposal.localizedData,
|
|
321
|
+
mode: "update"
|
|
322
|
+
});
|
|
323
|
+
if (preparedData.issues.length > 0 || !preparedData.localizedData) {
|
|
324
|
+
return failApply({
|
|
325
|
+
debug: createApplyDebugPayload({
|
|
326
|
+
details: {
|
|
327
|
+
issues: preparedData.issues
|
|
328
|
+
},
|
|
329
|
+
phase: "apply_validation",
|
|
330
|
+
proposal,
|
|
331
|
+
reason: "invalid_global_write_shape"
|
|
332
|
+
}),
|
|
333
|
+
error: "Proposal is invalid.",
|
|
334
|
+
logMessage: "AI apply blocked: invalid global proposal data",
|
|
335
|
+
proposal
|
|
336
|
+
});
|
|
337
|
+
}
|
|
233
338
|
const defaultLocaleDoc = defaultLocale ? await req.payload.findGlobal({
|
|
234
339
|
depth: 2,
|
|
235
340
|
fallbackLocale: false,
|
|
@@ -237,8 +342,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
237
342
|
req,
|
|
238
343
|
slug: proposal.slug
|
|
239
344
|
}) : null;
|
|
240
|
-
|
|
241
|
-
const localeNormalized = normalizeDataForFields(globalFields, localeData);
|
|
345
|
+
const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
|
|
242
346
|
const before = await req.payload.findGlobal({
|
|
243
347
|
depth: 2,
|
|
244
348
|
fallbackLocale: false,
|
|
@@ -246,10 +350,10 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
246
350
|
req,
|
|
247
351
|
slug: proposal.slug
|
|
248
352
|
});
|
|
249
|
-
const completedData =
|
|
250
|
-
data: localeNormalized.data,
|
|
353
|
+
const completedData = applyLocalizedRequiredFallbackToPreparedData({
|
|
251
354
|
fallbackSource: locale === defaultLocale ? before : defaultLocaleDoc || before,
|
|
252
|
-
fields: globalFields
|
|
355
|
+
fields: globalFields,
|
|
356
|
+
preparedData: localeData
|
|
253
357
|
});
|
|
254
358
|
await req.payload.updateGlobal({
|
|
255
359
|
data: completedData,
|
|
@@ -258,9 +362,16 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
258
362
|
req,
|
|
259
363
|
slug: proposal.slug
|
|
260
364
|
});
|
|
365
|
+
return {
|
|
366
|
+
before,
|
|
367
|
+
completedData,
|
|
368
|
+
locale
|
|
369
|
+
};
|
|
370
|
+
}));
|
|
371
|
+
localizedResults.forEach(({ before, completedData, locale })=>{
|
|
261
372
|
beforeByLocale[locale] = before;
|
|
262
373
|
afterByLocale[locale] = mergeData(before, completedData);
|
|
263
|
-
}
|
|
374
|
+
});
|
|
264
375
|
const change = await logAIChange({
|
|
265
376
|
changeLogCollection: options.changeLogCollection,
|
|
266
377
|
context: logContext,
|
|
@@ -271,13 +382,43 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
271
382
|
before: beforeByLocale
|
|
272
383
|
}
|
|
273
384
|
});
|
|
385
|
+
logHandlerEvent(req, "info", {
|
|
386
|
+
changeLogged: Boolean(change),
|
|
387
|
+
locales: Object.keys(proposal.localizedData),
|
|
388
|
+
msg: "AI apply succeeded",
|
|
389
|
+
proposal: getProposalLogSummary(proposal)
|
|
390
|
+
});
|
|
274
391
|
return Response.json({
|
|
275
392
|
change,
|
|
276
393
|
doc: undefined,
|
|
277
394
|
status: "applied"
|
|
278
395
|
});
|
|
279
396
|
}
|
|
280
|
-
|
|
397
|
+
const preparedData = prepareProposalWriteData({
|
|
398
|
+
collectionConfig: {
|
|
399
|
+
fields: globalConfig?.fields || [],
|
|
400
|
+
slug: proposal.slug
|
|
401
|
+
},
|
|
402
|
+
data: proposal.data,
|
|
403
|
+
inferenceText,
|
|
404
|
+
label: proposal.label,
|
|
405
|
+
mode: "update"
|
|
406
|
+
});
|
|
407
|
+
if (preparedData.issues.length > 0 || !preparedData.data) {
|
|
408
|
+
return failApply({
|
|
409
|
+
debug: createApplyDebugPayload({
|
|
410
|
+
details: {
|
|
411
|
+
issues: preparedData.issues
|
|
412
|
+
},
|
|
413
|
+
phase: "apply_validation",
|
|
414
|
+
proposal,
|
|
415
|
+
reason: "invalid_global_write_shape"
|
|
416
|
+
}),
|
|
417
|
+
error: "Proposal is invalid.",
|
|
418
|
+
logMessage: "AI apply blocked: invalid global proposal data",
|
|
419
|
+
proposal
|
|
420
|
+
});
|
|
421
|
+
}
|
|
281
422
|
const before = await req.payload.findGlobal({
|
|
282
423
|
depth: 2,
|
|
283
424
|
...proposal.locale ? {
|
|
@@ -287,7 +428,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
287
428
|
slug: proposal.slug
|
|
288
429
|
});
|
|
289
430
|
const doc = await req.payload.updateGlobal({
|
|
290
|
-
data:
|
|
431
|
+
data: preparedData.data,
|
|
291
432
|
...proposal.locale ? {
|
|
292
433
|
locale: proposal.locale
|
|
293
434
|
} : {},
|
|
@@ -301,21 +442,34 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
301
442
|
proposal,
|
|
302
443
|
req,
|
|
303
444
|
target: {
|
|
304
|
-
after: mergeData(before,
|
|
445
|
+
after: mergeData(before, preparedData.data),
|
|
305
446
|
before
|
|
306
447
|
}
|
|
307
448
|
});
|
|
449
|
+
logHandlerEvent(req, "info", {
|
|
450
|
+
changeLogged: Boolean(change),
|
|
451
|
+
locale: proposal.locale,
|
|
452
|
+
msg: "AI apply succeeded",
|
|
453
|
+
proposal: getProposalLogSummary(proposal)
|
|
454
|
+
});
|
|
308
455
|
return Response.json({
|
|
309
456
|
change,
|
|
310
457
|
doc: getAppliedDocReference(doc),
|
|
311
458
|
status: "applied"
|
|
312
459
|
});
|
|
313
460
|
}
|
|
314
|
-
if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action))
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
461
|
+
if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action)) {
|
|
462
|
+
return failApply({
|
|
463
|
+
debug: createApplyDebugPayload({
|
|
464
|
+
phase: "authorization",
|
|
465
|
+
proposal,
|
|
466
|
+
reason: "unknown_or_disallowed_collection"
|
|
467
|
+
}),
|
|
468
|
+
error: "Unknown collection",
|
|
469
|
+
logMessage: "AI apply blocked: unknown or disallowed collection",
|
|
470
|
+
proposal
|
|
471
|
+
});
|
|
472
|
+
}
|
|
319
473
|
if (proposal.action === "delete") {
|
|
320
474
|
const doc = await req.payload.delete({
|
|
321
475
|
collection: proposal.collection,
|
|
@@ -334,6 +488,12 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
334
488
|
documentID: proposal.id
|
|
335
489
|
}
|
|
336
490
|
});
|
|
491
|
+
logHandlerEvent(req, "info", {
|
|
492
|
+
changeLogged: Boolean(change),
|
|
493
|
+
documentID: proposal.id,
|
|
494
|
+
msg: "AI apply succeeded",
|
|
495
|
+
proposal: getProposalLogSummary(proposal)
|
|
496
|
+
});
|
|
337
497
|
return Response.json({
|
|
338
498
|
change,
|
|
339
499
|
doc: getAppliedDocReference(doc),
|
|
@@ -341,35 +501,48 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
341
501
|
});
|
|
342
502
|
}
|
|
343
503
|
const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
|
|
344
|
-
const collectionFields =
|
|
345
|
-
const normalizeCollectionData = (data)=>normalizeAuthData(collectionConfig, normalizeDataForFields(collectionFields, data));
|
|
504
|
+
const collectionFields = getSchemaFields(collectionConfig);
|
|
346
505
|
if (hasLocalizedData(proposal)) {
|
|
506
|
+
const preparedData = prepareProposalWriteData({
|
|
507
|
+
collectionConfig,
|
|
508
|
+
inferenceText,
|
|
509
|
+
label: proposal.label,
|
|
510
|
+
localizedData: proposal.localizedData,
|
|
511
|
+
mode: proposal.action
|
|
512
|
+
});
|
|
513
|
+
if (preparedData.issues.length > 0 || !preparedData.localizedData) {
|
|
514
|
+
return failApply({
|
|
515
|
+
debug: createApplyDebugPayload({
|
|
516
|
+
details: {
|
|
517
|
+
issues: preparedData.issues
|
|
518
|
+
},
|
|
519
|
+
phase: "apply_validation",
|
|
520
|
+
proposal,
|
|
521
|
+
reason: "invalid_collection_write_shape"
|
|
522
|
+
}),
|
|
523
|
+
error: "Proposal is invalid.",
|
|
524
|
+
logMessage: "AI apply blocked: invalid collection proposal data",
|
|
525
|
+
proposal
|
|
526
|
+
});
|
|
527
|
+
}
|
|
347
528
|
if (proposal.action === "create") {
|
|
348
|
-
const localeEntries = Object.entries(
|
|
529
|
+
const localeEntries = Object.entries(preparedData.localizedData);
|
|
349
530
|
const [firstLocale, firstLocaleData] = localeEntries[0] || [];
|
|
350
|
-
if (!firstLocale)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
531
|
+
if (!firstLocale) {
|
|
532
|
+
return failApply({
|
|
533
|
+
debug: createApplyDebugPayload({
|
|
534
|
+
phase: "apply_validation",
|
|
535
|
+
proposal,
|
|
536
|
+
reason: "localized_create_without_locales"
|
|
537
|
+
}),
|
|
538
|
+
error: "Proposal is invalid.",
|
|
539
|
+
logMessage: "AI apply blocked: localized create proposal has no locales",
|
|
540
|
+
proposal
|
|
368
541
|
});
|
|
369
542
|
}
|
|
370
543
|
const doc = await req.payload.create({
|
|
371
544
|
collection: proposal.collection,
|
|
372
|
-
data:
|
|
545
|
+
data: firstLocaleData,
|
|
373
546
|
locale: firstLocale,
|
|
374
547
|
overrideAccess: false,
|
|
375
548
|
req
|
|
@@ -380,13 +553,12 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
380
553
|
const afterByLocale = {
|
|
381
554
|
[firstLocale]: doc
|
|
382
555
|
};
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
const completedData = applyLocalizedRequiredFallback({
|
|
387
|
-
data: localeNormalized.data,
|
|
556
|
+
const fallbackSource = doc;
|
|
557
|
+
const localizedResults = await Promise.all(localeEntries.slice(1).map(async ([locale, localeData])=>{
|
|
558
|
+
const completedData = applyLocalizedRequiredFallbackToPreparedData({
|
|
388
559
|
fallbackSource,
|
|
389
|
-
fields: collectionFields
|
|
560
|
+
fields: collectionFields,
|
|
561
|
+
preparedData: localeData
|
|
390
562
|
});
|
|
391
563
|
await req.payload.update({
|
|
392
564
|
collection: proposal.collection,
|
|
@@ -396,9 +568,15 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
396
568
|
overrideAccess: false,
|
|
397
569
|
req
|
|
398
570
|
});
|
|
571
|
+
return {
|
|
572
|
+
completedData,
|
|
573
|
+
locale
|
|
574
|
+
};
|
|
575
|
+
}));
|
|
576
|
+
localizedResults.forEach(({ completedData, locale })=>{
|
|
399
577
|
beforeByLocale[locale] = {};
|
|
400
578
|
afterByLocale[locale] = completedData;
|
|
401
|
-
}
|
|
579
|
+
});
|
|
402
580
|
const change = await logAIChange({
|
|
403
581
|
changeLogCollection: options.changeLogCollection,
|
|
404
582
|
context: logContext,
|
|
@@ -410,6 +588,13 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
410
588
|
documentID: doc.id
|
|
411
589
|
}
|
|
412
590
|
});
|
|
591
|
+
logHandlerEvent(req, "info", {
|
|
592
|
+
changeLogged: Boolean(change),
|
|
593
|
+
documentID: doc.id,
|
|
594
|
+
locales: localeEntries.map(([locale])=>locale),
|
|
595
|
+
msg: "AI apply succeeded",
|
|
596
|
+
proposal: getProposalLogSummary(proposal)
|
|
597
|
+
});
|
|
413
598
|
return Response.json({
|
|
414
599
|
change,
|
|
415
600
|
doc: getAppliedDocReference(doc),
|
|
@@ -426,8 +611,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
426
611
|
locale: defaultLocale,
|
|
427
612
|
req
|
|
428
613
|
}) : null;
|
|
429
|
-
|
|
430
|
-
const localeNormalized = normalizeCollectionData(localeData);
|
|
614
|
+
const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
|
|
431
615
|
const before = await req.payload.findByID({
|
|
432
616
|
collection: proposal.collection,
|
|
433
617
|
depth: 2,
|
|
@@ -436,10 +620,10 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
436
620
|
locale,
|
|
437
621
|
req
|
|
438
622
|
});
|
|
439
|
-
const completedData =
|
|
440
|
-
data: localeNormalized.data,
|
|
623
|
+
const completedData = applyLocalizedRequiredFallbackToPreparedData({
|
|
441
624
|
fallbackSource: locale === defaultLocale ? before : defaultLocaleDoc || before,
|
|
442
|
-
fields: collectionFields
|
|
625
|
+
fields: collectionFields,
|
|
626
|
+
preparedData: localeData
|
|
443
627
|
});
|
|
444
628
|
await req.payload.update({
|
|
445
629
|
collection: proposal.collection,
|
|
@@ -449,9 +633,16 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
449
633
|
overrideAccess: false,
|
|
450
634
|
req
|
|
451
635
|
});
|
|
636
|
+
return {
|
|
637
|
+
before,
|
|
638
|
+
completedData,
|
|
639
|
+
locale
|
|
640
|
+
};
|
|
641
|
+
}));
|
|
642
|
+
localizedResults.forEach(({ before, completedData, locale })=>{
|
|
452
643
|
beforeByLocale[locale] = before;
|
|
453
644
|
afterByLocale[locale] = mergeData(before, completedData);
|
|
454
|
-
}
|
|
645
|
+
});
|
|
455
646
|
const change = await logAIChange({
|
|
456
647
|
changeLogCollection: options.changeLogCollection,
|
|
457
648
|
context: logContext,
|
|
@@ -463,6 +654,13 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
463
654
|
documentID: proposal.id
|
|
464
655
|
}
|
|
465
656
|
});
|
|
657
|
+
logHandlerEvent(req, "info", {
|
|
658
|
+
changeLogged: Boolean(change),
|
|
659
|
+
documentID: proposal.id,
|
|
660
|
+
locales: Object.keys(proposal.localizedData),
|
|
661
|
+
msg: "AI apply succeeded",
|
|
662
|
+
proposal: getProposalLogSummary(proposal)
|
|
663
|
+
});
|
|
466
664
|
return Response.json({
|
|
467
665
|
change,
|
|
468
666
|
doc: {
|
|
@@ -471,25 +669,32 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
471
669
|
status: "applied"
|
|
472
670
|
});
|
|
473
671
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
672
|
+
const preparedData = prepareProposalWriteData({
|
|
673
|
+
collectionConfig,
|
|
674
|
+
data: proposal.data,
|
|
675
|
+
inferenceText,
|
|
676
|
+
label: proposal.label,
|
|
677
|
+
mode: proposal.action
|
|
678
|
+
});
|
|
679
|
+
if (preparedData.issues.length > 0 || !preparedData.data) {
|
|
680
|
+
return failApply({
|
|
681
|
+
debug: createApplyDebugPayload({
|
|
682
|
+
details: {
|
|
683
|
+
issues: preparedData.issues
|
|
684
|
+
},
|
|
685
|
+
phase: "apply_validation",
|
|
686
|
+
proposal,
|
|
687
|
+
reason: "invalid_collection_write_shape"
|
|
688
|
+
}),
|
|
689
|
+
error: "Proposal is invalid.",
|
|
690
|
+
logMessage: "AI apply blocked: invalid collection proposal data",
|
|
691
|
+
proposal
|
|
487
692
|
});
|
|
488
693
|
}
|
|
489
694
|
if (proposal.action === "create") {
|
|
490
695
|
const doc = await req.payload.create({
|
|
491
696
|
collection: proposal.collection,
|
|
492
|
-
data:
|
|
697
|
+
data: preparedData.data,
|
|
493
698
|
...proposal.locale ? {
|
|
494
699
|
locale: proposal.locale
|
|
495
700
|
} : {},
|
|
@@ -507,6 +712,12 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
507
712
|
documentID: doc.id
|
|
508
713
|
}
|
|
509
714
|
});
|
|
715
|
+
logHandlerEvent(req, "info", {
|
|
716
|
+
changeLogged: Boolean(change),
|
|
717
|
+
documentID: doc.id,
|
|
718
|
+
msg: "AI apply succeeded",
|
|
719
|
+
proposal: getProposalLogSummary(proposal)
|
|
720
|
+
});
|
|
510
721
|
return Response.json({
|
|
511
722
|
change,
|
|
512
723
|
doc: getAppliedDocReference(doc),
|
|
@@ -524,7 +735,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
524
735
|
});
|
|
525
736
|
const doc = await req.payload.update({
|
|
526
737
|
collection: proposal.collection,
|
|
527
|
-
data:
|
|
738
|
+
data: preparedData.data,
|
|
528
739
|
id: proposal.id,
|
|
529
740
|
...proposal.locale ? {
|
|
530
741
|
locale: proposal.locale
|
|
@@ -538,23 +749,41 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
538
749
|
proposal,
|
|
539
750
|
req,
|
|
540
751
|
target: {
|
|
541
|
-
after: mergeData(before,
|
|
752
|
+
after: mergeData(before, preparedData.data),
|
|
542
753
|
before,
|
|
543
754
|
documentID: proposal.id
|
|
544
755
|
}
|
|
545
756
|
});
|
|
757
|
+
logHandlerEvent(req, "info", {
|
|
758
|
+
changeLogged: Boolean(change),
|
|
759
|
+
documentID: proposal.id,
|
|
760
|
+
msg: "AI apply succeeded",
|
|
761
|
+
proposal: getProposalLogSummary(proposal)
|
|
762
|
+
});
|
|
546
763
|
return Response.json({
|
|
547
764
|
change,
|
|
548
765
|
doc: getAppliedDocReference(doc),
|
|
549
766
|
status: "applied"
|
|
550
767
|
});
|
|
551
768
|
} catch (err) {
|
|
769
|
+
const debug = createApplyDebugPayload({
|
|
770
|
+
details: err && typeof err === "object" && "data" in err && isRecord(err.data) ? {
|
|
771
|
+
payloadError: err.data
|
|
772
|
+
} : undefined,
|
|
773
|
+
phase: "payload_operation",
|
|
774
|
+
proposal,
|
|
775
|
+
reason: "payload_operation_failed"
|
|
776
|
+
});
|
|
552
777
|
req.payload.logger.error({
|
|
778
|
+
debug,
|
|
553
779
|
err,
|
|
554
780
|
msg: "AI apply action failed",
|
|
555
|
-
|
|
781
|
+
promptPreview: getLogPreview(logContext.prompt),
|
|
782
|
+
proposal: getProposalMeta(proposal),
|
|
783
|
+
tokenUsage: logContext.tokenUsage
|
|
556
784
|
});
|
|
557
785
|
return Response.json({
|
|
786
|
+
debug,
|
|
558
787
|
error: "Could not apply proposal."
|
|
559
788
|
}, {
|
|
560
789
|
status: 400
|