@mvriu5/payload-ai 1.6.2 → 1.6.4

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 (126) hide show
  1. package/dist/components/action-toast/ActionToast.d.ts +1 -14
  2. package/dist/components/action-toast/ActionToast.js +1 -1
  3. package/dist/components/ai-input/AIInput.js +1 -1
  4. package/dist/components/audit-log-list/AuditLogList.d.ts +2 -3
  5. package/dist/components/diff-dialog/DiffDialog.d.ts +2 -6
  6. package/dist/components/diff-dialog/DiffDialog.js +1 -1
  7. package/dist/components/generate-field/GenerateField.js +2 -0
  8. package/dist/components/hooks/useAIChatStream.d.ts +1 -1
  9. package/dist/components/hooks/useAISettings.d.ts +1 -1
  10. package/dist/components/hooks/useAISettings.js +1 -1
  11. package/dist/components/hooks/useDocumentMentionSuggestions.js +1 -1
  12. package/dist/components/hooks/useMentions.js +1 -1
  13. package/dist/components/hooks/usePluginConfig.d.ts +6 -6
  14. package/dist/components/hooks/usePluginConfig.js +1 -1
  15. package/dist/{payload → features}/collectionPermissions.js +1 -1
  16. package/dist/{payload/textFieldGeneration.d.ts → features/content/fieldGeneration.d.ts} +4 -0
  17. package/dist/{payload/textFieldGeneration.js → features/content/fieldGeneration.js} +19 -3
  18. package/dist/{ai → features}/promptCaching.d.ts +2 -2
  19. package/dist/features/proposals/containerNormalization.d.ts +8 -0
  20. package/dist/features/proposals/containerNormalization.js +112 -0
  21. package/dist/{payload/proposalData.d.ts → features/proposals/data.d.ts} +5 -10
  22. package/dist/features/proposals/data.js +113 -0
  23. package/dist/features/proposals/fieldNormalization.d.ts +2 -0
  24. package/dist/features/proposals/fieldNormalization.js +69 -0
  25. package/dist/features/proposals/normalizationTypes.d.ts +31 -0
  26. package/dist/features/proposals/normalizationTypes.js +1 -0
  27. package/dist/features/proposals/optionInference.d.ts +13 -0
  28. package/dist/features/proposals/optionInference.js +46 -0
  29. package/dist/features/proposals/recordNormalization.d.ts +6 -0
  30. package/dist/features/proposals/recordNormalization.js +78 -0
  31. package/dist/features/proposals/relationshipNormalization.d.ts +8 -0
  32. package/dist/features/proposals/relationshipNormalization.js +71 -0
  33. package/dist/{ai/proposalRepair.d.ts → features/proposals/repair.d.ts} +1 -1
  34. package/dist/{ai/proposalSigning.d.ts → features/proposals/signing.d.ts} +2 -5
  35. package/dist/{ai/proposalSigning.js → features/proposals/signing.js} +1 -1
  36. package/dist/features/proposals/target.d.ts +52 -0
  37. package/dist/features/proposals/target.js +125 -0
  38. package/dist/features/proposals/types.d.ts +49 -0
  39. package/dist/features/proposals/types.js +1 -0
  40. package/dist/features/proposals/validation.d.ts +12 -0
  41. package/dist/features/proposals/validation.js +26 -0
  42. package/dist/features/providers/requestContext.d.ts +53 -0
  43. package/dist/features/providers/requestContext.js +90 -0
  44. package/dist/{ai/providerRuntime.d.ts → features/providers/runtime.d.ts} +1 -1
  45. package/dist/{ai/providerRuntime.js → features/providers/runtime.js} +1 -1
  46. package/dist/features/schema/context.d.ts +7 -0
  47. package/dist/features/schema/context.js +11 -0
  48. package/dist/{payload/schemaContext.d.ts → features/schema/description.d.ts} +6 -33
  49. package/dist/{payload/schemaContext.js → features/schema/description.js} +3 -207
  50. package/dist/{payload/toolFieldSelection.d.ts → features/schema/fieldSelection.d.ts} +1 -1
  51. package/dist/{payload/toolFieldSelection.js → features/schema/fieldSelection.js} +2 -2
  52. package/dist/features/schema/mentionDocuments.d.ts +15 -0
  53. package/dist/features/schema/mentionDocuments.js +50 -0
  54. package/dist/features/schema/mentions.d.ts +18 -0
  55. package/dist/features/schema/mentions.js +100 -0
  56. package/dist/{payload/normalizeData.js → features/schema/normalize.js} +1 -1
  57. package/dist/features/schema/registry.d.ts +30 -0
  58. package/dist/features/schema/registry.js +73 -0
  59. package/dist/{payload → features/schema}/toolSchemas.d.ts +1 -1
  60. package/dist/{payload → features/schema}/toolSchemas.js +1 -1
  61. package/dist/{ai → features}/sensitiveData.js +1 -1
  62. package/dist/{ai → features}/tokenUsage.d.ts +1 -2
  63. package/dist/handlers/apply/audit.d.ts +42 -0
  64. package/dist/handlers/apply/audit.js +150 -0
  65. package/dist/handlers/apply/operations.d.ts +18 -0
  66. package/dist/handlers/apply/operations.js +377 -0
  67. package/dist/handlers/apply/types.d.ts +33 -0
  68. package/dist/handlers/apply/types.js +1 -0
  69. package/dist/handlers/apply/validation.d.ts +45 -0
  70. package/dist/handlers/apply/validation.js +125 -0
  71. package/dist/handlers/applyActionHandler.d.ts +1 -6
  72. package/dist/handlers/applyActionHandler.js +13 -757
  73. package/dist/handlers/auditLogHandler.js +4 -8
  74. package/dist/handlers/chat/intent.d.ts +32 -0
  75. package/dist/handlers/chat/intent.js +155 -0
  76. package/dist/handlers/chat/prompt.d.ts +20 -0
  77. package/dist/handlers/chat/prompt.js +55 -0
  78. package/dist/handlers/chat/proposalTools.d.ts +96 -0
  79. package/dist/handlers/chat/proposalTools.js +451 -0
  80. package/dist/handlers/chat/streaming.d.ts +38 -0
  81. package/dist/handlers/chat/streaming.js +136 -0
  82. package/dist/handlers/chat/toolFactory.d.ts +79 -0
  83. package/dist/handlers/chat/toolFactory.js +156 -0
  84. package/dist/handlers/chat/types.d.ts +57 -0
  85. package/dist/handlers/chat/types.js +1 -0
  86. package/dist/handlers/chatHandler.d.ts +1 -44
  87. package/dist/handlers/chatHandler.js +186 -1162
  88. package/dist/handlers/generateFieldHandler.d.ts +3 -8
  89. package/dist/handlers/generateFieldHandler.js +71 -99
  90. package/dist/handlers/http.d.ts +17 -0
  91. package/dist/handlers/http.js +21 -0
  92. package/dist/handlers/mediaUploadHandler.js +12 -34
  93. package/dist/handlers/mentionSuggestionHandler.d.ts +1 -1
  94. package/dist/handlers/mentionSuggestionHandler.js +6 -10
  95. package/dist/handlers/proposalDiffHandler.d.ts +1 -1
  96. package/dist/handlers/proposalDiffHandler.js +72 -195
  97. package/dist/handlers/translateDocumentHandler.d.ts +3 -8
  98. package/dist/handlers/translateDocumentHandler.js +26 -82
  99. package/dist/index.d.ts +4 -30
  100. package/dist/index.js +31 -421
  101. package/dist/plugin/admin.d.ts +30 -0
  102. package/dist/plugin/admin.js +155 -0
  103. package/dist/plugin/collections.d.ts +3 -0
  104. package/dist/plugin/collections.js +169 -0
  105. package/dist/plugin/endpoints.d.ts +23 -0
  106. package/dist/plugin/endpoints.js +84 -0
  107. package/dist/plugin/options.d.ts +4 -0
  108. package/dist/plugin/options.js +13 -0
  109. package/dist/plugin/types.d.ts +28 -0
  110. package/dist/plugin/types.js +1 -0
  111. package/dist/{payload/shared.d.ts → utils/data.d.ts} +2 -12
  112. package/package.json +1 -1
  113. package/dist/payload/proposalData.js +0 -578
  114. /package/dist/{payload → features}/collectionPermissions.d.ts +0 -0
  115. /package/dist/{payload → features/content}/documentTranslation.d.ts +0 -0
  116. /package/dist/{payload → features/content}/documentTranslation.js +0 -0
  117. /package/dist/{ai → features}/promptCaching.js +0 -0
  118. /package/dist/{ai/proposalRepair.js → features/proposals/repair.js} +0 -0
  119. /package/dist/{ai/providerOptions.d.ts → features/providers/options.d.ts} +0 -0
  120. /package/dist/{ai/providerOptions.js → features/providers/options.js} +0 -0
  121. /package/dist/{payload/normalizeData.d.ts → features/schema/normalize.d.ts} +0 -0
  122. /package/dist/{ai → features}/sensitiveData.d.ts +0 -0
  123. /package/dist/{ai → features}/tokenUsage.js +0 -0
  124. /package/dist/{payload/shared.js → utils/data.js} +0 -0
  125. /package/dist/{payload → utils}/logging.d.ts +0 -0
  126. /package/dist/{payload → utils}/logging.js +0 -0
@@ -0,0 +1,451 @@
1
+ import { getOptionValue, getSafeProposalLabel, hasValueAtPath, isRecord, setValueAtPath } from "../../utils/data.js";
2
+ export const getRequiredFieldInfos = (fields, titleFieldName, path = "")=>{
3
+ return fields.flatMap((field)=>{
4
+ const fieldPath = field.name ? path ? `${path}.${field.name}` : field.name : path;
5
+ const requiredField = field.required && fieldPath ? [
6
+ {
7
+ defaultValue: field.defaultValue,
8
+ isTitleField: field.name === titleFieldName,
9
+ localized: Boolean(field.localized),
10
+ options: field.options,
11
+ path: fieldPath,
12
+ type: field.type
13
+ }
14
+ ] : [];
15
+ const nestedFields = field.fields?.length ? getRequiredFieldInfos(field.fields, titleFieldName, fieldPath) : [];
16
+ const nestedBlocks = field.blocks?.length ? field.blocks.flatMap((block)=>getRequiredFieldInfos(block.fields || [], titleFieldName, `${fieldPath}.${block.slug}`)) : [];
17
+ return [
18
+ ...requiredField,
19
+ ...nestedFields,
20
+ ...nestedBlocks
21
+ ];
22
+ });
23
+ };
24
+ const getCreateFallbackValue = ({ field, label })=>{
25
+ if (field.defaultValue !== undefined) return field.defaultValue;
26
+ if (field.path === "_status") return "draft";
27
+ if (field.type === "checkbox") return false;
28
+ if (field.type === "select" || field.type === "radio") {
29
+ return getOptionValue(field.options?.[0]);
30
+ }
31
+ const terminalSegment = field.path.split(".").at(-1)?.toLowerCase();
32
+ if ((field.isTitleField || [
33
+ "title",
34
+ "name",
35
+ "label",
36
+ "headline"
37
+ ].includes(terminalSegment || "")) && [
38
+ "text",
39
+ "textarea"
40
+ ].includes(field.type || "")) {
41
+ return getSafeProposalLabel(label);
42
+ }
43
+ return undefined;
44
+ };
45
+ export const fillMissingCreateFields = ({ data, label, localizedData, requiredFields })=>{
46
+ if (localizedData) {
47
+ const completedLocalizedData = Object.fromEntries(Object.entries(localizedData).map(([locale, localeData])=>[
48
+ locale,
49
+ {
50
+ ...localeData
51
+ }
52
+ ]));
53
+ const localeEntries = Object.entries(completedLocalizedData);
54
+ const [firstLocale, firstLocaleData] = localeEntries[0] || [];
55
+ for (const [locale, localeData] of localeEntries){
56
+ for (const field of requiredFields.filter((item)=>item.localized)){
57
+ if (hasValueAtPath(localeData, field.path)) continue;
58
+ const fallbackValue = getCreateFallbackValue({
59
+ field,
60
+ label
61
+ });
62
+ if (fallbackValue !== undefined) {
63
+ setValueAtPath(localeData, field.path, fallbackValue);
64
+ }
65
+ }
66
+ }
67
+ if (firstLocale && firstLocaleData) {
68
+ for (const field of requiredFields.filter((item)=>!item.localized)){
69
+ if (hasValueAtPath(firstLocaleData, field.path)) continue;
70
+ const fallbackValue = getCreateFallbackValue({
71
+ field,
72
+ label
73
+ });
74
+ if (fallbackValue !== undefined) {
75
+ setValueAtPath(firstLocaleData, field.path, fallbackValue);
76
+ }
77
+ }
78
+ }
79
+ return {
80
+ localizedData: completedLocalizedData
81
+ };
82
+ }
83
+ const completedData = {
84
+ ...data || {}
85
+ };
86
+ for (const field of requiredFields){
87
+ if (hasValueAtPath(completedData, field.path)) continue;
88
+ const fallbackValue = getCreateFallbackValue({
89
+ field,
90
+ label
91
+ });
92
+ if (fallbackValue !== undefined) {
93
+ setValueAtPath(completedData, field.path, fallbackValue);
94
+ }
95
+ }
96
+ return {
97
+ data: completedData
98
+ };
99
+ };
100
+ export const getMissingCreateFields = ({ data, localizedData, requiredFields })=>{
101
+ if (localizedData) {
102
+ const locales = Object.entries(localizedData);
103
+ const firstLocale = locales[0];
104
+ if (!firstLocale) {
105
+ return [
106
+ "localizedData must include at least one locale entry"
107
+ ];
108
+ }
109
+ const missing = [];
110
+ const localizedRequiredFields = [];
111
+ const sharedRequiredFields = [];
112
+ for (const field of requiredFields){
113
+ if (field.localized) {
114
+ localizedRequiredFields.push(field);
115
+ } else {
116
+ sharedRequiredFields.push(field);
117
+ }
118
+ }
119
+ for (const [locale, localeData] of locales){
120
+ for (const field of localizedRequiredFields){
121
+ if (!hasValueAtPath(localeData, field.path)) {
122
+ missing.push(`${locale}:${field.path}`);
123
+ }
124
+ }
125
+ }
126
+ for (const field of sharedRequiredFields){
127
+ if (!hasValueAtPath(firstLocale[1], field.path)) {
128
+ missing.push(`${firstLocale[0]}:${field.path}`);
129
+ }
130
+ }
131
+ return missing;
132
+ }
133
+ if (!data) return [
134
+ "data is required"
135
+ ];
136
+ return requiredFields.flatMap((field)=>hasValueAtPath(data, field.path) ? [] : [
137
+ field.path
138
+ ]);
139
+ };
140
+ export const getProposalSummary = (proposal)=>({
141
+ action: proposal.action,
142
+ collection: "collection" in proposal ? proposal.collection : undefined,
143
+ hasData: "data" in proposal && Boolean(proposal.data),
144
+ hasLocalizedData: "localizedData" in proposal && Boolean(proposal.localizedData),
145
+ id: "id" in proposal ? proposal.id : undefined,
146
+ label: proposal.label,
147
+ locale: proposal.locale,
148
+ locales: "localizedData" in proposal && proposal.localizedData ? Object.keys(proposal.localizedData) : undefined,
149
+ slug: "slug" in proposal ? proposal.slug : undefined
150
+ });
151
+ export const getCollectionBlockTypes = (fields)=>{
152
+ const blockTypes = new Set();
153
+ const visitFields = (items)=>{
154
+ for (const field of items){
155
+ if (field.type === "blocks" && field.blocks) {
156
+ for (const block of field.blocks){
157
+ blockTypes.add(block.slug);
158
+ if (block.fields?.length) {
159
+ visitFields(block.fields);
160
+ }
161
+ }
162
+ }
163
+ if (field.fields?.length) {
164
+ visitFields(field.fields);
165
+ }
166
+ }
167
+ };
168
+ visitFields(fields);
169
+ return [
170
+ ...blockTypes
171
+ ];
172
+ };
173
+ const regexSpecialCharactersPattern = /[.*+?^${}()|[\]\\]/g;
174
+ export const getRequestedBlockTypes = ({ availableBlockTypes, mentions, prompt })=>{
175
+ const requestedBlockTypes = new Set();
176
+ const normalizedPrompt = prompt.toLowerCase();
177
+ const availableBlockTypeSet = new Set(availableBlockTypes);
178
+ const blockTypesByNormalizedSlug = new Map(availableBlockTypes.map((blockType)=>[
179
+ blockType.toLowerCase(),
180
+ blockType
181
+ ]));
182
+ const escapedBlockTypes = availableBlockTypes.map((blockType)=>blockType.replace(regexSpecialCharactersPattern, "\\$&")).join("|");
183
+ const blockPattern = escapedBlockTypes ? new RegExp(`\\b(${escapedBlockTypes})\\b(?:\\s+block)?`, "gi") : null;
184
+ for (const mention of mentions || []){
185
+ if (mention.type === "block" && mention.slug && availableBlockTypeSet.has(mention.slug)) {
186
+ requestedBlockTypes.add(mention.slug);
187
+ }
188
+ }
189
+ if (blockPattern) {
190
+ for (const match of normalizedPrompt.matchAll(blockPattern)){
191
+ const blockType = match[1] ? blockTypesByNormalizedSlug.get(match[1].toLowerCase()) : null;
192
+ if (blockType) {
193
+ requestedBlockTypes.add(blockType);
194
+ }
195
+ }
196
+ }
197
+ return [
198
+ ...requestedBlockTypes
199
+ ];
200
+ };
201
+ export const collectProposalBlockTypes = ({ data, fields })=>{
202
+ const foundBlockTypes = new Set();
203
+ const visitFields = (items, value)=>{
204
+ for (const field of items){
205
+ if (!field.name) continue;
206
+ const fieldValue = value[field.name];
207
+ if (fieldValue === undefined || fieldValue === null) continue;
208
+ if (field.type === "blocks" && Array.isArray(fieldValue)) {
209
+ const blocksBySlug = new Map(field.blocks?.map((block)=>[
210
+ block.slug,
211
+ block
212
+ ]));
213
+ for (const blockItem of fieldValue){
214
+ if (!isRecord(blockItem)) continue;
215
+ const blockType = typeof blockItem.blockType === "string" ? blockItem.blockType : typeof blockItem.type === "string" ? blockItem.type : typeof blockItem.slug === "string" ? blockItem.slug : null;
216
+ if (blockType) {
217
+ foundBlockTypes.add(blockType);
218
+ }
219
+ if (blockType) {
220
+ const blockConfig = blocksBySlug.get(blockType);
221
+ if (blockConfig?.fields?.length) {
222
+ visitFields(blockConfig.fields, blockItem);
223
+ }
224
+ }
225
+ }
226
+ continue;
227
+ }
228
+ if (field.type === "group" && isRecord(fieldValue) && field.fields?.length) {
229
+ visitFields(field.fields, fieldValue);
230
+ continue;
231
+ }
232
+ if (field.type === "array" && Array.isArray(fieldValue) && field.fields?.length) {
233
+ for (const item of fieldValue){
234
+ if (isRecord(item)) {
235
+ visitFields(field.fields, item);
236
+ }
237
+ }
238
+ }
239
+ }
240
+ };
241
+ visitFields(fields, data);
242
+ return foundBlockTypes;
243
+ };
244
+ const collectRelationshipTargets = ({ data, fields, path = "" })=>{
245
+ const targets = [];
246
+ for (const field of fields){
247
+ if (!field.name) continue;
248
+ const fieldPath = path ? `${path}.${field.name}` : field.name;
249
+ const fieldValue = data[field.name];
250
+ if (fieldValue === undefined || fieldValue === null) continue;
251
+ if (field.type === "relationship" || field.type === "upload") {
252
+ const relationTargets = Array.isArray(field.relationTo) ? field.relationTo.filter((item)=>typeof item === "string") : typeof field.relationTo === "string" ? [
253
+ field.relationTo
254
+ ] : [];
255
+ const collectSingle = (value, itemPath)=>{
256
+ if (typeof value === "string" || typeof value === "number") {
257
+ if (relationTargets.length === 1) {
258
+ targets.push({
259
+ collection: relationTargets[0],
260
+ id: value,
261
+ path: itemPath
262
+ });
263
+ }
264
+ return;
265
+ }
266
+ if (!isRecord(value)) return;
267
+ const relationTo = typeof value.relationTo === "string" ? value.relationTo : relationTargets[0];
268
+ const id = typeof value.id === "string" || typeof value.id === "number" ? value.id : typeof value.value === "string" || typeof value.value === "number" ? value.value : undefined;
269
+ if (!relationTo || id === undefined) return;
270
+ targets.push({
271
+ collection: relationTo,
272
+ id,
273
+ path: itemPath
274
+ });
275
+ };
276
+ if (field.hasMany && Array.isArray(fieldValue)) {
277
+ fieldValue.forEach((item, index)=>collectSingle(item, `${fieldPath}.${index}`));
278
+ } else {
279
+ collectSingle(fieldValue, fieldPath);
280
+ }
281
+ continue;
282
+ }
283
+ if (field.type === "group" && isRecord(fieldValue) && field.fields?.length) {
284
+ targets.push(...collectRelationshipTargets({
285
+ data: fieldValue,
286
+ fields: field.fields,
287
+ path: fieldPath
288
+ }));
289
+ continue;
290
+ }
291
+ if (field.type === "array" && Array.isArray(fieldValue) && field.fields?.length) {
292
+ fieldValue.forEach((item, index)=>{
293
+ if (!isRecord(item)) return;
294
+ targets.push(...collectRelationshipTargets({
295
+ data: item,
296
+ fields: field.fields,
297
+ path: `${fieldPath}.${index}`
298
+ }));
299
+ });
300
+ continue;
301
+ }
302
+ if (field.type === "blocks" && Array.isArray(fieldValue) && field.blocks?.length) {
303
+ const blocksBySlug = new Map(field.blocks.map((block)=>[
304
+ block.slug,
305
+ block
306
+ ]));
307
+ fieldValue.forEach((item, index)=>{
308
+ if (!isRecord(item)) return;
309
+ const blockType = typeof item.blockType === "string" ? item.blockType : typeof item.type === "string" ? item.type : typeof item.slug === "string" ? item.slug : null;
310
+ if (!blockType) return;
311
+ const blockConfig = blocksBySlug.get(blockType);
312
+ if (!blockConfig?.fields?.length) return;
313
+ targets.push(...collectRelationshipTargets({
314
+ data: item,
315
+ fields: blockConfig.fields,
316
+ path: `${fieldPath}.${index}`
317
+ }));
318
+ });
319
+ }
320
+ }
321
+ return targets;
322
+ };
323
+ const collectUploadTargets = ({ data, fields, path = "" })=>{
324
+ const targets = [];
325
+ for (const field of fields){
326
+ if (!field.name) continue;
327
+ const fieldPath = path ? `${path}.${field.name}` : field.name;
328
+ const fieldValue = data[field.name];
329
+ if (fieldValue === undefined || fieldValue === null) continue;
330
+ if (field.type === "upload") {
331
+ const relationTargets = Array.isArray(field.relationTo) ? field.relationTo.filter((item)=>typeof item === "string") : typeof field.relationTo === "string" ? [
332
+ field.relationTo
333
+ ] : [];
334
+ const collectSingle = (value, itemPath)=>{
335
+ if (typeof value === "string" || typeof value === "number") {
336
+ if (relationTargets.length === 1) {
337
+ targets.push({
338
+ collection: relationTargets[0],
339
+ id: value,
340
+ path: itemPath
341
+ });
342
+ }
343
+ return;
344
+ }
345
+ if (!isRecord(value)) return;
346
+ const relationTo = typeof value.relationTo === "string" ? value.relationTo : relationTargets[0];
347
+ const id = typeof value.id === "string" || typeof value.id === "number" ? value.id : typeof value.value === "string" || typeof value.value === "number" ? value.value : undefined;
348
+ if (!relationTo || id === undefined) return;
349
+ targets.push({
350
+ collection: relationTo,
351
+ id,
352
+ path: itemPath
353
+ });
354
+ };
355
+ if (field.hasMany && Array.isArray(fieldValue)) {
356
+ fieldValue.forEach((item, index)=>collectSingle(item, `${fieldPath}.${index}`));
357
+ } else {
358
+ collectSingle(fieldValue, fieldPath);
359
+ }
360
+ continue;
361
+ }
362
+ if (field.type === "group" && isRecord(fieldValue) && field.fields?.length) {
363
+ targets.push(...collectUploadTargets({
364
+ data: fieldValue,
365
+ fields: field.fields,
366
+ path: fieldPath
367
+ }));
368
+ continue;
369
+ }
370
+ if (field.type === "array" && Array.isArray(fieldValue) && field.fields?.length) {
371
+ fieldValue.forEach((item, index)=>{
372
+ if (!isRecord(item)) return;
373
+ targets.push(...collectUploadTargets({
374
+ data: item,
375
+ fields: field.fields,
376
+ path: `${fieldPath}.${index}`
377
+ }));
378
+ });
379
+ continue;
380
+ }
381
+ if (field.type === "blocks" && Array.isArray(fieldValue) && field.blocks?.length) {
382
+ const blocksBySlug = new Map(field.blocks.map((block)=>[
383
+ block.slug,
384
+ block
385
+ ]));
386
+ fieldValue.forEach((item, index)=>{
387
+ if (!isRecord(item)) return;
388
+ const blockType = typeof item.blockType === "string" ? item.blockType : typeof item.type === "string" ? item.type : typeof item.slug === "string" ? item.slug : null;
389
+ if (!blockType) return;
390
+ const blockConfig = blocksBySlug.get(blockType);
391
+ if (!blockConfig?.fields?.length) return;
392
+ targets.push(...collectUploadTargets({
393
+ data: item,
394
+ fields: blockConfig.fields,
395
+ path: `${fieldPath}.${index}`
396
+ }));
397
+ });
398
+ }
399
+ }
400
+ return targets;
401
+ };
402
+ export const validateRelationshipTargetsExist = async ({ data, fields, req })=>{
403
+ const targets = collectRelationshipTargets({
404
+ data,
405
+ fields
406
+ });
407
+ const targetResults = await Promise.all(targets.map(async (target)=>{
408
+ try {
409
+ await req.payload.findByID({
410
+ collection: target.collection,
411
+ depth: 0,
412
+ id: String(target.id),
413
+ overrideAccess: false,
414
+ req
415
+ });
416
+ return null;
417
+ } catch {
418
+ return target;
419
+ }
420
+ }));
421
+ return targetResults.filter((target)=>Boolean(target));
422
+ };
423
+ export const getUploadTargetsOutsideAttachments = ({ allowedAttachmentKeys, data, fields })=>{
424
+ if (allowedAttachmentKeys.size === 0) return [];
425
+ return collectUploadTargets({
426
+ data,
427
+ fields
428
+ }).filter((target)=>!allowedAttachmentKeys.has(`${target.collection}:${String(target.id)}`));
429
+ };
430
+ export const validateProposalReferences = async ({ allowedAttachmentKeys, data, fields, localizedData, priority, req })=>{
431
+ const records = [
432
+ ...data ? [
433
+ data
434
+ ] : [],
435
+ ...Object.values(localizedData || {})
436
+ ];
437
+ const uploadTargetsOutsideAttachments = records.flatMap((record)=>getUploadTargetsOutsideAttachments({
438
+ allowedAttachmentKeys,
439
+ data: record,
440
+ fields
441
+ }));
442
+ const invalidRelationshipTargets = priority === "attachments" && uploadTargetsOutsideAttachments.length > 0 ? [] : (await Promise.all(records.map((record)=>validateRelationshipTargetsExist({
443
+ data: record,
444
+ fields,
445
+ req
446
+ })))).flat();
447
+ return {
448
+ invalidRelationshipTargets,
449
+ uploadTargetsOutsideAttachments
450
+ };
451
+ };
@@ -0,0 +1,38 @@
1
+ import type { ChatDebug, TokenUsage, ToolFailure } from "./types.js";
2
+ type ChatDebugPayload = {
3
+ activeLocale?: string;
4
+ model: string;
5
+ proposalCount: number;
6
+ provider: string;
7
+ reason: "model_did_not_call_tool" | "proposal_created" | "tool_validation_failed" | "write_intent_without_tool_call";
8
+ selectedLocales: string[];
9
+ toolFailures: ToolFailure[];
10
+ usage?: TokenUsage | null;
11
+ };
12
+ export declare const createInitialChatDebug: ({ model, provider }: {
13
+ model: string;
14
+ provider: string;
15
+ }) => ChatDebug;
16
+ export declare const createSSEEventStream: (events: Array<{
17
+ data: unknown;
18
+ event: string;
19
+ }>) => ReadableStream<Uint8Array<ArrayBufferLike>>;
20
+ export declare const getChatCompletionReason: ({ proposalCount, toolFailures, writeIntent, }: {
21
+ proposalCount: number;
22
+ toolFailures: ToolFailure[];
23
+ writeIntent: boolean;
24
+ }) => "model_did_not_call_tool" | "proposal_created" | "tool_validation_failed" | "write_intent_without_tool_call";
25
+ export declare const createDebugPayload: ({ activeLocale, debug, proposalCount, selectedLocales, toolFailures, usage, writeIntent, }: {
26
+ activeLocale?: string;
27
+ debug: ChatDebug;
28
+ proposalCount: number;
29
+ selectedLocales: string[];
30
+ toolFailures: ToolFailure[];
31
+ usage?: TokenUsage | null;
32
+ writeIntent: boolean;
33
+ }) => ChatDebugPayload;
34
+ export declare const createE2EChatResponse: ({ prompt, selectedLocales }: {
35
+ prompt: string;
36
+ selectedLocales: string[];
37
+ }) => Response;
38
+ export {};
@@ -0,0 +1,136 @@
1
+ import { signAIActionProposal } from "../../features/proposals/signing.js";
2
+ export const createInitialChatDebug = ({ model, provider })=>({
3
+ model,
4
+ provider,
5
+ tools: [
6
+ "getDoc",
7
+ "getGlobal",
8
+ "listCollections",
9
+ "listGlobals",
10
+ "proposeCreateDoc",
11
+ "proposeDeleteDoc",
12
+ "proposeUpdateDoc",
13
+ "proposeUpdateGlobal",
14
+ "searchDocs"
15
+ ]
16
+ });
17
+ export const createSSEEventStream = (events)=>{
18
+ const encoder = new TextEncoder();
19
+ return new ReadableStream({
20
+ start (controller) {
21
+ for (const { data, event } of events){
22
+ controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
23
+ }
24
+ controller.close();
25
+ }
26
+ });
27
+ };
28
+ export const getChatCompletionReason = ({ proposalCount, toolFailures, writeIntent })=>{
29
+ if (proposalCount > 0) return "proposal_created";
30
+ if (toolFailures.length > 0) return "tool_validation_failed";
31
+ if (writeIntent) return "write_intent_without_tool_call";
32
+ return "model_did_not_call_tool";
33
+ };
34
+ export const createDebugPayload = ({ activeLocale, debug, proposalCount, selectedLocales, toolFailures, usage, writeIntent })=>({
35
+ activeLocale,
36
+ model: debug.model,
37
+ proposalCount,
38
+ provider: debug.provider,
39
+ reason: getChatCompletionReason({
40
+ proposalCount,
41
+ toolFailures,
42
+ writeIntent
43
+ }),
44
+ selectedLocales,
45
+ toolFailures,
46
+ usage
47
+ });
48
+ export const createE2EChatResponse = ({ prompt, selectedLocales })=>{
49
+ const normalizedPrompt = prompt.toLowerCase();
50
+ const wantsCreatePost = normalizedPrompt.includes("post") && (normalizedPrompt.includes("create") || normalizedPrompt.includes("erstell") || normalizedPrompt.includes("apply flow") || normalizedPrompt.includes("locale review") || normalizedPrompt.includes("proposal review"));
51
+ const mentionsMars = normalizedPrompt.includes("mars");
52
+ const multipleLocales = selectedLocales.length > 1;
53
+ const activeLocale = selectedLocales.at(-1);
54
+ const proposalLabel = multipleLocales ? normalizedPrompt.includes("locale review") ? "Create localized locale review draft post about Mars" : "Create localized draft post about Mars" : normalizedPrompt.includes("apply flow") ? "Create apply flow draft post about Mars" : normalizedPrompt.includes("proposal review") ? "Create proposal review draft post about Mars" : "Create draft post about Mars";
55
+ const proposal = wantsCreatePost && mentionsMars ? signAIActionProposal(multipleLocales ? {
56
+ action: "create",
57
+ collection: "posts",
58
+ label: proposalLabel,
59
+ localizedData: Object.fromEntries(selectedLocales.map((locale)=>[
60
+ locale,
61
+ {
62
+ content: locale === "de" ? "Mars ist der vierte Planet von der Sonne." : "Mars is the fourth planet from the Sun.",
63
+ excerpt: locale === "de" ? "Ein kurzer Entwurf ueber Mars." : "A short draft about Mars.",
64
+ title: locale === "de" ? "Mars im Ueberblick" : "Mars Overview"
65
+ }
66
+ ])),
67
+ ...activeLocale ? {
68
+ locale: activeLocale
69
+ } : {}
70
+ } : {
71
+ action: "create",
72
+ collection: "posts",
73
+ data: {
74
+ content: "Mars is the fourth planet from the Sun.",
75
+ excerpt: "A short draft about Mars.",
76
+ status: "draft",
77
+ title: "Mars Overview"
78
+ },
79
+ label: proposalLabel,
80
+ ...activeLocale ? {
81
+ locale: activeLocale
82
+ } : {}
83
+ }) : null;
84
+ const responseText = proposal ? "Prepared one draft post proposal." : "No content change was proposed.";
85
+ const debugPayload = createDebugPayload({
86
+ debug: {
87
+ model: "e2e-model",
88
+ provider: "openai",
89
+ tools: []
90
+ },
91
+ proposalCount: proposal ? 1 : 0,
92
+ selectedLocales,
93
+ toolFailures: [],
94
+ usage: {
95
+ inputTokens: 42,
96
+ outputTokens: 27,
97
+ totalTokens: 69
98
+ },
99
+ writeIntent: wantsCreatePost
100
+ });
101
+ return new Response(createSSEEventStream([
102
+ {
103
+ data: {
104
+ delta: responseText
105
+ },
106
+ event: "text"
107
+ },
108
+ {
109
+ data: {
110
+ proposals: proposal ? [
111
+ proposal
112
+ ] : [],
113
+ usage: {
114
+ inputTokens: 42,
115
+ outputTokens: 27,
116
+ totalTokens: 69
117
+ }
118
+ },
119
+ event: "proposals"
120
+ },
121
+ {
122
+ data: debugPayload,
123
+ event: "debug"
124
+ },
125
+ {
126
+ data: {},
127
+ event: "done"
128
+ }
129
+ ]), {
130
+ headers: {
131
+ "Cache-Control": "no-cache, no-transform",
132
+ Connection: "keep-alive",
133
+ "Content-Type": "text/event-stream; charset=utf-8"
134
+ }
135
+ });
136
+ };