@mvriu5/payload-ai 1.1.1 → 1.2.0
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/dist/components/AIInput.js +155 -6
- package/dist/components/AIInput.module.css +11 -2
- package/dist/components/ActionToast.d.ts +2 -1
- package/dist/components/ActionToast.js +3 -2
- package/dist/handlers/applyActionHandler.js +316 -107
- package/dist/handlers/chatHandler.js +660 -48
- package/dist/handlers/proposalDiffHandler.js +81 -26
- package/dist/payload/logging.d.ts +9 -0
- package/dist/payload/logging.js +14 -0
- package/dist/payload/normalizeData.d.ts +17 -7
- package/dist/payload/normalizeData.js +22 -1
- package/dist/payload/proposalData.d.ts +31 -0
- package/dist/payload/proposalData.js +575 -0
- package/dist/payload/schemaContext.d.ts +3 -7
- package/dist/payload/schemaContext.js +49 -23
- package/package.json +1 -1
|
@@ -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
|
-
for (const [locale, localeData] of Object.entries(
|
|
241
|
-
const localeNormalized = normalizeDataForFields(globalFields, localeData);
|
|
345
|
+
for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
|
|
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,
|
|
@@ -271,13 +375,43 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
271
375
|
before: beforeByLocale
|
|
272
376
|
}
|
|
273
377
|
});
|
|
378
|
+
logHandlerEvent(req, "info", {
|
|
379
|
+
changeLogged: Boolean(change),
|
|
380
|
+
locales: Object.keys(proposal.localizedData),
|
|
381
|
+
msg: "AI apply succeeded",
|
|
382
|
+
proposal: getProposalLogSummary(proposal)
|
|
383
|
+
});
|
|
274
384
|
return Response.json({
|
|
275
385
|
change,
|
|
276
386
|
doc: undefined,
|
|
277
387
|
status: "applied"
|
|
278
388
|
});
|
|
279
389
|
}
|
|
280
|
-
|
|
390
|
+
const preparedData = prepareProposalWriteData({
|
|
391
|
+
collectionConfig: {
|
|
392
|
+
fields: globalConfig?.fields || [],
|
|
393
|
+
slug: proposal.slug
|
|
394
|
+
},
|
|
395
|
+
data: proposal.data,
|
|
396
|
+
inferenceText,
|
|
397
|
+
label: proposal.label,
|
|
398
|
+
mode: "update"
|
|
399
|
+
});
|
|
400
|
+
if (preparedData.issues.length > 0 || !preparedData.data) {
|
|
401
|
+
return failApply({
|
|
402
|
+
debug: createApplyDebugPayload({
|
|
403
|
+
details: {
|
|
404
|
+
issues: preparedData.issues
|
|
405
|
+
},
|
|
406
|
+
phase: "apply_validation",
|
|
407
|
+
proposal,
|
|
408
|
+
reason: "invalid_global_write_shape"
|
|
409
|
+
}),
|
|
410
|
+
error: "Proposal is invalid.",
|
|
411
|
+
logMessage: "AI apply blocked: invalid global proposal data",
|
|
412
|
+
proposal
|
|
413
|
+
});
|
|
414
|
+
}
|
|
281
415
|
const before = await req.payload.findGlobal({
|
|
282
416
|
depth: 2,
|
|
283
417
|
...proposal.locale ? {
|
|
@@ -287,7 +421,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
287
421
|
slug: proposal.slug
|
|
288
422
|
});
|
|
289
423
|
const doc = await req.payload.updateGlobal({
|
|
290
|
-
data:
|
|
424
|
+
data: preparedData.data,
|
|
291
425
|
...proposal.locale ? {
|
|
292
426
|
locale: proposal.locale
|
|
293
427
|
} : {},
|
|
@@ -301,21 +435,34 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
301
435
|
proposal,
|
|
302
436
|
req,
|
|
303
437
|
target: {
|
|
304
|
-
after: mergeData(before,
|
|
438
|
+
after: mergeData(before, preparedData.data),
|
|
305
439
|
before
|
|
306
440
|
}
|
|
307
441
|
});
|
|
442
|
+
logHandlerEvent(req, "info", {
|
|
443
|
+
changeLogged: Boolean(change),
|
|
444
|
+
locale: proposal.locale,
|
|
445
|
+
msg: "AI apply succeeded",
|
|
446
|
+
proposal: getProposalLogSummary(proposal)
|
|
447
|
+
});
|
|
308
448
|
return Response.json({
|
|
309
449
|
change,
|
|
310
450
|
doc: getAppliedDocReference(doc),
|
|
311
451
|
status: "applied"
|
|
312
452
|
});
|
|
313
453
|
}
|
|
314
|
-
if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action))
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
454
|
+
if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action)) {
|
|
455
|
+
return failApply({
|
|
456
|
+
debug: createApplyDebugPayload({
|
|
457
|
+
phase: "authorization",
|
|
458
|
+
proposal,
|
|
459
|
+
reason: "unknown_or_disallowed_collection"
|
|
460
|
+
}),
|
|
461
|
+
error: "Unknown collection",
|
|
462
|
+
logMessage: "AI apply blocked: unknown or disallowed collection",
|
|
463
|
+
proposal
|
|
464
|
+
});
|
|
465
|
+
}
|
|
319
466
|
if (proposal.action === "delete") {
|
|
320
467
|
const doc = await req.payload.delete({
|
|
321
468
|
collection: proposal.collection,
|
|
@@ -334,6 +481,12 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
334
481
|
documentID: proposal.id
|
|
335
482
|
}
|
|
336
483
|
});
|
|
484
|
+
logHandlerEvent(req, "info", {
|
|
485
|
+
changeLogged: Boolean(change),
|
|
486
|
+
documentID: proposal.id,
|
|
487
|
+
msg: "AI apply succeeded",
|
|
488
|
+
proposal: getProposalLogSummary(proposal)
|
|
489
|
+
});
|
|
337
490
|
return Response.json({
|
|
338
491
|
change,
|
|
339
492
|
doc: getAppliedDocReference(doc),
|
|
@@ -341,35 +494,48 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
341
494
|
});
|
|
342
495
|
}
|
|
343
496
|
const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
|
|
344
|
-
const collectionFields =
|
|
345
|
-
const normalizeCollectionData = (data)=>normalizeAuthData(collectionConfig, normalizeDataForFields(collectionFields, data));
|
|
497
|
+
const collectionFields = getSchemaFields(collectionConfig);
|
|
346
498
|
if (hasLocalizedData(proposal)) {
|
|
499
|
+
const preparedData = prepareProposalWriteData({
|
|
500
|
+
collectionConfig,
|
|
501
|
+
inferenceText,
|
|
502
|
+
label: proposal.label,
|
|
503
|
+
localizedData: proposal.localizedData,
|
|
504
|
+
mode: proposal.action
|
|
505
|
+
});
|
|
506
|
+
if (preparedData.issues.length > 0 || !preparedData.localizedData) {
|
|
507
|
+
return failApply({
|
|
508
|
+
debug: createApplyDebugPayload({
|
|
509
|
+
details: {
|
|
510
|
+
issues: preparedData.issues
|
|
511
|
+
},
|
|
512
|
+
phase: "apply_validation",
|
|
513
|
+
proposal,
|
|
514
|
+
reason: "invalid_collection_write_shape"
|
|
515
|
+
}),
|
|
516
|
+
error: "Proposal is invalid.",
|
|
517
|
+
logMessage: "AI apply blocked: invalid collection proposal data",
|
|
518
|
+
proposal
|
|
519
|
+
});
|
|
520
|
+
}
|
|
347
521
|
if (proposal.action === "create") {
|
|
348
|
-
const localeEntries = Object.entries(
|
|
522
|
+
const localeEntries = Object.entries(preparedData.localizedData);
|
|
349
523
|
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
|
|
524
|
+
if (!firstLocale) {
|
|
525
|
+
return failApply({
|
|
526
|
+
debug: createApplyDebugPayload({
|
|
527
|
+
phase: "apply_validation",
|
|
528
|
+
proposal,
|
|
529
|
+
reason: "localized_create_without_locales"
|
|
530
|
+
}),
|
|
531
|
+
error: "Proposal is invalid.",
|
|
532
|
+
logMessage: "AI apply blocked: localized create proposal has no locales",
|
|
533
|
+
proposal
|
|
368
534
|
});
|
|
369
535
|
}
|
|
370
536
|
const doc = await req.payload.create({
|
|
371
537
|
collection: proposal.collection,
|
|
372
|
-
data:
|
|
538
|
+
data: firstLocaleData,
|
|
373
539
|
locale: firstLocale,
|
|
374
540
|
overrideAccess: false,
|
|
375
541
|
req
|
|
@@ -382,11 +548,10 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
382
548
|
};
|
|
383
549
|
let fallbackSource = doc;
|
|
384
550
|
for (const [locale, localeData] of localeEntries.slice(1)){
|
|
385
|
-
const
|
|
386
|
-
const completedData = applyLocalizedRequiredFallback({
|
|
387
|
-
data: localeNormalized.data,
|
|
551
|
+
const completedData = applyLocalizedRequiredFallbackToPreparedData({
|
|
388
552
|
fallbackSource,
|
|
389
|
-
fields: collectionFields
|
|
553
|
+
fields: collectionFields,
|
|
554
|
+
preparedData: localeData
|
|
390
555
|
});
|
|
391
556
|
await req.payload.update({
|
|
392
557
|
collection: proposal.collection,
|
|
@@ -410,6 +575,13 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
410
575
|
documentID: doc.id
|
|
411
576
|
}
|
|
412
577
|
});
|
|
578
|
+
logHandlerEvent(req, "info", {
|
|
579
|
+
changeLogged: Boolean(change),
|
|
580
|
+
documentID: doc.id,
|
|
581
|
+
locales: localeEntries.map(([locale])=>locale),
|
|
582
|
+
msg: "AI apply succeeded",
|
|
583
|
+
proposal: getProposalLogSummary(proposal)
|
|
584
|
+
});
|
|
413
585
|
return Response.json({
|
|
414
586
|
change,
|
|
415
587
|
doc: getAppliedDocReference(doc),
|
|
@@ -426,8 +598,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
426
598
|
locale: defaultLocale,
|
|
427
599
|
req
|
|
428
600
|
}) : null;
|
|
429
|
-
for (const [locale, localeData] of Object.entries(
|
|
430
|
-
const localeNormalized = normalizeCollectionData(localeData);
|
|
601
|
+
for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
|
|
431
602
|
const before = await req.payload.findByID({
|
|
432
603
|
collection: proposal.collection,
|
|
433
604
|
depth: 2,
|
|
@@ -436,10 +607,10 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
436
607
|
locale,
|
|
437
608
|
req
|
|
438
609
|
});
|
|
439
|
-
const completedData =
|
|
440
|
-
data: localeNormalized.data,
|
|
610
|
+
const completedData = applyLocalizedRequiredFallbackToPreparedData({
|
|
441
611
|
fallbackSource: locale === defaultLocale ? before : defaultLocaleDoc || before,
|
|
442
|
-
fields: collectionFields
|
|
612
|
+
fields: collectionFields,
|
|
613
|
+
preparedData: localeData
|
|
443
614
|
});
|
|
444
615
|
await req.payload.update({
|
|
445
616
|
collection: proposal.collection,
|
|
@@ -463,6 +634,13 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
463
634
|
documentID: proposal.id
|
|
464
635
|
}
|
|
465
636
|
});
|
|
637
|
+
logHandlerEvent(req, "info", {
|
|
638
|
+
changeLogged: Boolean(change),
|
|
639
|
+
documentID: proposal.id,
|
|
640
|
+
locales: Object.keys(proposal.localizedData),
|
|
641
|
+
msg: "AI apply succeeded",
|
|
642
|
+
proposal: getProposalLogSummary(proposal)
|
|
643
|
+
});
|
|
466
644
|
return Response.json({
|
|
467
645
|
change,
|
|
468
646
|
doc: {
|
|
@@ -471,25 +649,32 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
471
649
|
status: "applied"
|
|
472
650
|
});
|
|
473
651
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
652
|
+
const preparedData = prepareProposalWriteData({
|
|
653
|
+
collectionConfig,
|
|
654
|
+
data: proposal.data,
|
|
655
|
+
inferenceText,
|
|
656
|
+
label: proposal.label,
|
|
657
|
+
mode: proposal.action
|
|
658
|
+
});
|
|
659
|
+
if (preparedData.issues.length > 0 || !preparedData.data) {
|
|
660
|
+
return failApply({
|
|
661
|
+
debug: createApplyDebugPayload({
|
|
662
|
+
details: {
|
|
663
|
+
issues: preparedData.issues
|
|
664
|
+
},
|
|
665
|
+
phase: "apply_validation",
|
|
666
|
+
proposal,
|
|
667
|
+
reason: "invalid_collection_write_shape"
|
|
668
|
+
}),
|
|
669
|
+
error: "Proposal is invalid.",
|
|
670
|
+
logMessage: "AI apply blocked: invalid collection proposal data",
|
|
671
|
+
proposal
|
|
487
672
|
});
|
|
488
673
|
}
|
|
489
674
|
if (proposal.action === "create") {
|
|
490
675
|
const doc = await req.payload.create({
|
|
491
676
|
collection: proposal.collection,
|
|
492
|
-
data:
|
|
677
|
+
data: preparedData.data,
|
|
493
678
|
...proposal.locale ? {
|
|
494
679
|
locale: proposal.locale
|
|
495
680
|
} : {},
|
|
@@ -507,6 +692,12 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
507
692
|
documentID: doc.id
|
|
508
693
|
}
|
|
509
694
|
});
|
|
695
|
+
logHandlerEvent(req, "info", {
|
|
696
|
+
changeLogged: Boolean(change),
|
|
697
|
+
documentID: doc.id,
|
|
698
|
+
msg: "AI apply succeeded",
|
|
699
|
+
proposal: getProposalLogSummary(proposal)
|
|
700
|
+
});
|
|
510
701
|
return Response.json({
|
|
511
702
|
change,
|
|
512
703
|
doc: getAppliedDocReference(doc),
|
|
@@ -524,7 +715,7 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
524
715
|
});
|
|
525
716
|
const doc = await req.payload.update({
|
|
526
717
|
collection: proposal.collection,
|
|
527
|
-
data:
|
|
718
|
+
data: preparedData.data,
|
|
528
719
|
id: proposal.id,
|
|
529
720
|
...proposal.locale ? {
|
|
530
721
|
locale: proposal.locale
|
|
@@ -538,23 +729,41 @@ export const createApplyActionHandler = (options = {})=>async (req)=>{
|
|
|
538
729
|
proposal,
|
|
539
730
|
req,
|
|
540
731
|
target: {
|
|
541
|
-
after: mergeData(before,
|
|
732
|
+
after: mergeData(before, preparedData.data),
|
|
542
733
|
before,
|
|
543
734
|
documentID: proposal.id
|
|
544
735
|
}
|
|
545
736
|
});
|
|
737
|
+
logHandlerEvent(req, "info", {
|
|
738
|
+
changeLogged: Boolean(change),
|
|
739
|
+
documentID: proposal.id,
|
|
740
|
+
msg: "AI apply succeeded",
|
|
741
|
+
proposal: getProposalLogSummary(proposal)
|
|
742
|
+
});
|
|
546
743
|
return Response.json({
|
|
547
744
|
change,
|
|
548
745
|
doc: getAppliedDocReference(doc),
|
|
549
746
|
status: "applied"
|
|
550
747
|
});
|
|
551
748
|
} catch (err) {
|
|
749
|
+
const debug = createApplyDebugPayload({
|
|
750
|
+
details: err && typeof err === "object" && "data" in err && isRecord(err.data) ? {
|
|
751
|
+
payloadError: err.data
|
|
752
|
+
} : undefined,
|
|
753
|
+
phase: "payload_operation",
|
|
754
|
+
proposal,
|
|
755
|
+
reason: "payload_operation_failed"
|
|
756
|
+
});
|
|
552
757
|
req.payload.logger.error({
|
|
758
|
+
debug,
|
|
553
759
|
err,
|
|
554
760
|
msg: "AI apply action failed",
|
|
555
|
-
|
|
761
|
+
promptPreview: getLogPreview(logContext.prompt),
|
|
762
|
+
proposal: getProposalMeta(proposal),
|
|
763
|
+
tokenUsage: logContext.tokenUsage
|
|
556
764
|
});
|
|
557
765
|
return Response.json({
|
|
766
|
+
debug,
|
|
558
767
|
error: "Could not apply proposal."
|
|
559
768
|
}, {
|
|
560
769
|
status: 400
|