@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.
- package/dist/components/action-toast/ActionToast.d.ts +1 -14
- package/dist/components/action-toast/ActionToast.js +1 -1
- package/dist/components/ai-input/AIInput.js +1 -1
- package/dist/components/audit-log-list/AuditLogList.d.ts +2 -3
- package/dist/components/diff-dialog/DiffDialog.d.ts +2 -6
- package/dist/components/diff-dialog/DiffDialog.js +1 -1
- package/dist/components/generate-field/GenerateField.js +2 -0
- package/dist/components/hooks/useAIChatStream.d.ts +1 -1
- package/dist/components/hooks/useAISettings.d.ts +1 -1
- package/dist/components/hooks/useAISettings.js +1 -1
- package/dist/components/hooks/useDocumentMentionSuggestions.js +1 -1
- package/dist/components/hooks/useMentions.js +1 -1
- package/dist/components/hooks/usePluginConfig.d.ts +6 -6
- package/dist/components/hooks/usePluginConfig.js +1 -1
- package/dist/{payload → features}/collectionPermissions.js +1 -1
- package/dist/{payload/textFieldGeneration.d.ts → features/content/fieldGeneration.d.ts} +4 -0
- package/dist/{payload/textFieldGeneration.js → features/content/fieldGeneration.js} +19 -3
- package/dist/{ai → features}/promptCaching.d.ts +2 -2
- package/dist/features/proposals/containerNormalization.d.ts +8 -0
- package/dist/features/proposals/containerNormalization.js +112 -0
- package/dist/{payload/proposalData.d.ts → features/proposals/data.d.ts} +5 -10
- package/dist/features/proposals/data.js +113 -0
- package/dist/features/proposals/fieldNormalization.d.ts +2 -0
- package/dist/features/proposals/fieldNormalization.js +69 -0
- package/dist/features/proposals/normalizationTypes.d.ts +31 -0
- package/dist/features/proposals/normalizationTypes.js +1 -0
- package/dist/features/proposals/optionInference.d.ts +13 -0
- package/dist/features/proposals/optionInference.js +46 -0
- package/dist/features/proposals/recordNormalization.d.ts +6 -0
- package/dist/features/proposals/recordNormalization.js +78 -0
- package/dist/features/proposals/relationshipNormalization.d.ts +8 -0
- package/dist/features/proposals/relationshipNormalization.js +71 -0
- package/dist/{ai/proposalRepair.d.ts → features/proposals/repair.d.ts} +1 -1
- package/dist/{ai/proposalSigning.d.ts → features/proposals/signing.d.ts} +2 -5
- package/dist/{ai/proposalSigning.js → features/proposals/signing.js} +1 -1
- package/dist/features/proposals/target.d.ts +52 -0
- package/dist/features/proposals/target.js +125 -0
- package/dist/features/proposals/types.d.ts +49 -0
- package/dist/features/proposals/types.js +1 -0
- package/dist/features/proposals/validation.d.ts +12 -0
- package/dist/features/proposals/validation.js +26 -0
- package/dist/features/providers/requestContext.d.ts +53 -0
- package/dist/features/providers/requestContext.js +90 -0
- package/dist/{ai/providerRuntime.d.ts → features/providers/runtime.d.ts} +1 -1
- package/dist/{ai/providerRuntime.js → features/providers/runtime.js} +1 -1
- package/dist/features/schema/context.d.ts +7 -0
- package/dist/features/schema/context.js +11 -0
- package/dist/{payload/schemaContext.d.ts → features/schema/description.d.ts} +6 -33
- package/dist/{payload/schemaContext.js → features/schema/description.js} +3 -207
- package/dist/{payload/toolFieldSelection.d.ts → features/schema/fieldSelection.d.ts} +1 -1
- package/dist/{payload/toolFieldSelection.js → features/schema/fieldSelection.js} +2 -2
- package/dist/features/schema/mentionDocuments.d.ts +15 -0
- package/dist/features/schema/mentionDocuments.js +50 -0
- package/dist/features/schema/mentions.d.ts +18 -0
- package/dist/features/schema/mentions.js +100 -0
- package/dist/{payload/normalizeData.js → features/schema/normalize.js} +1 -1
- package/dist/features/schema/registry.d.ts +30 -0
- package/dist/features/schema/registry.js +73 -0
- package/dist/{payload → features/schema}/toolSchemas.d.ts +1 -1
- package/dist/{payload → features/schema}/toolSchemas.js +1 -1
- package/dist/{ai → features}/sensitiveData.js +1 -1
- package/dist/{ai → features}/tokenUsage.d.ts +1 -2
- package/dist/handlers/apply/audit.d.ts +42 -0
- package/dist/handlers/apply/audit.js +150 -0
- package/dist/handlers/apply/operations.d.ts +18 -0
- package/dist/handlers/apply/operations.js +377 -0
- package/dist/handlers/apply/types.d.ts +33 -0
- package/dist/handlers/apply/types.js +1 -0
- package/dist/handlers/apply/validation.d.ts +45 -0
- package/dist/handlers/apply/validation.js +125 -0
- package/dist/handlers/applyActionHandler.d.ts +1 -6
- package/dist/handlers/applyActionHandler.js +13 -757
- package/dist/handlers/auditLogHandler.js +4 -8
- package/dist/handlers/chat/intent.d.ts +32 -0
- package/dist/handlers/chat/intent.js +155 -0
- package/dist/handlers/chat/prompt.d.ts +20 -0
- package/dist/handlers/chat/prompt.js +55 -0
- package/dist/handlers/chat/proposalTools.d.ts +96 -0
- package/dist/handlers/chat/proposalTools.js +451 -0
- package/dist/handlers/chat/streaming.d.ts +38 -0
- package/dist/handlers/chat/streaming.js +136 -0
- package/dist/handlers/chat/toolFactory.d.ts +79 -0
- package/dist/handlers/chat/toolFactory.js +156 -0
- package/dist/handlers/chat/types.d.ts +57 -0
- package/dist/handlers/chat/types.js +1 -0
- package/dist/handlers/chatHandler.d.ts +1 -44
- package/dist/handlers/chatHandler.js +186 -1162
- package/dist/handlers/generateFieldHandler.d.ts +3 -8
- package/dist/handlers/generateFieldHandler.js +71 -99
- package/dist/handlers/http.d.ts +17 -0
- package/dist/handlers/http.js +21 -0
- package/dist/handlers/mediaUploadHandler.js +12 -34
- package/dist/handlers/mentionSuggestionHandler.d.ts +1 -1
- package/dist/handlers/mentionSuggestionHandler.js +6 -10
- package/dist/handlers/proposalDiffHandler.d.ts +1 -1
- package/dist/handlers/proposalDiffHandler.js +72 -195
- package/dist/handlers/translateDocumentHandler.d.ts +3 -8
- package/dist/handlers/translateDocumentHandler.js +26 -82
- package/dist/index.d.ts +4 -30
- package/dist/index.js +31 -421
- package/dist/plugin/admin.d.ts +30 -0
- package/dist/plugin/admin.js +155 -0
- package/dist/plugin/collections.d.ts +3 -0
- package/dist/plugin/collections.js +169 -0
- package/dist/plugin/endpoints.d.ts +23 -0
- package/dist/plugin/endpoints.js +84 -0
- package/dist/plugin/options.d.ts +4 -0
- package/dist/plugin/options.js +13 -0
- package/dist/plugin/types.d.ts +28 -0
- package/dist/plugin/types.js +1 -0
- package/dist/{payload/shared.d.ts → utils/data.d.ts} +2 -12
- package/package.json +1 -1
- package/dist/payload/proposalData.js +0 -578
- /package/dist/{payload → features}/collectionPermissions.d.ts +0 -0
- /package/dist/{payload → features/content}/documentTranslation.d.ts +0 -0
- /package/dist/{payload → features/content}/documentTranslation.js +0 -0
- /package/dist/{ai → features}/promptCaching.js +0 -0
- /package/dist/{ai/proposalRepair.js → features/proposals/repair.js} +0 -0
- /package/dist/{ai/providerOptions.d.ts → features/providers/options.d.ts} +0 -0
- /package/dist/{ai/providerOptions.js → features/providers/options.js} +0 -0
- /package/dist/{payload/normalizeData.d.ts → features/schema/normalize.d.ts} +0 -0
- /package/dist/{ai → features}/sensitiveData.d.ts +0 -0
- /package/dist/{ai → features}/tokenUsage.js +0 -0
- /package/dist/{payload/shared.js → utils/data.js} +0 -0
- /package/dist/{payload → utils}/logging.d.ts +0 -0
- /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
|
+
};
|