@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
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type PayloadHandler } from "payload";
|
|
2
|
+
export type MediaUploadOptions = {
|
|
3
|
+
acceptedMimeTypes?: string[];
|
|
4
|
+
collectionSlug: string;
|
|
5
|
+
maxFileSize?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const createMediaUploadHandler: ({ acceptedMimeTypes, collectionSlug, maxFileSize }: MediaUploadOptions) => PayloadHandler;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { addDataAndFileToRequest } from "payload";
|
|
2
|
+
import { getNumber, getString, isRecord } from "../payload/shared.js";
|
|
3
|
+
const mimeTypeMatches = (mimeType, acceptedMimeType)=>{
|
|
4
|
+
if (acceptedMimeType === mimeType) return true;
|
|
5
|
+
if (!acceptedMimeType.endsWith("/*")) return false;
|
|
6
|
+
return mimeType.startsWith(`${acceptedMimeType.slice(0, -2)}/`);
|
|
7
|
+
};
|
|
8
|
+
const isAcceptedMimeType = (mimeType, acceptedMimeTypes)=>{
|
|
9
|
+
if (!acceptedMimeTypes || acceptedMimeTypes.length === 0) return true;
|
|
10
|
+
return acceptedMimeTypes.some((acceptedMimeType)=>mimeTypeMatches(mimeType, acceptedMimeType));
|
|
11
|
+
};
|
|
12
|
+
const getUploadData = (data)=>{
|
|
13
|
+
if (!isRecord(data)) return {};
|
|
14
|
+
const { collection: _collection, file: _file, ...uploadData } = data;
|
|
15
|
+
return uploadData;
|
|
16
|
+
};
|
|
17
|
+
const getMediaAttachment = ({ collectionSlug, doc, file })=>{
|
|
18
|
+
const record = isRecord(doc) ? doc : {};
|
|
19
|
+
const id = record.id;
|
|
20
|
+
return {
|
|
21
|
+
collection: collectionSlug,
|
|
22
|
+
filename: getString(record.filename) || file.name || "",
|
|
23
|
+
filesize: getNumber(record.filesize) || file.size || 0,
|
|
24
|
+
id: typeof id === "string" || typeof id === "number" ? String(id) : "",
|
|
25
|
+
mimeType: getString(record.mimeType) || file.mimetype || "",
|
|
26
|
+
type: "media",
|
|
27
|
+
url: getString(record.url)
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export const createMediaUploadHandler = ({ acceptedMimeTypes, collectionSlug, maxFileSize })=>async (req)=>{
|
|
31
|
+
const uploadReq = req;
|
|
32
|
+
try {
|
|
33
|
+
await addDataAndFileToRequest(uploadReq);
|
|
34
|
+
const collectionConfig = uploadReq.payload.config.collections.find((collection)=>collection.slug === collectionSlug);
|
|
35
|
+
if (!collectionConfig) {
|
|
36
|
+
return Response.json({
|
|
37
|
+
error: `Upload collection not found: ${collectionSlug}`
|
|
38
|
+
}, {
|
|
39
|
+
status: 400
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (!collectionConfig.upload) {
|
|
43
|
+
return Response.json({
|
|
44
|
+
error: `Collection is not upload-enabled: ${collectionSlug}`
|
|
45
|
+
}, {
|
|
46
|
+
status: 400
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const file = uploadReq.file;
|
|
50
|
+
if (!file) {
|
|
51
|
+
return Response.json({
|
|
52
|
+
error: "File is required"
|
|
53
|
+
}, {
|
|
54
|
+
status: 400
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const fileSize = typeof file.size === "number" && Number.isFinite(file.size) ? file.size : 0;
|
|
58
|
+
if (maxFileSize && fileSize > maxFileSize) {
|
|
59
|
+
return Response.json({
|
|
60
|
+
error: `File exceeds max size of ${maxFileSize} bytes`
|
|
61
|
+
}, {
|
|
62
|
+
status: 413
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const mimeType = file.mimetype || "";
|
|
66
|
+
if (mimeType && !isAcceptedMimeType(mimeType, acceptedMimeTypes)) {
|
|
67
|
+
return Response.json({
|
|
68
|
+
error: `File type is not accepted: ${mimeType}`
|
|
69
|
+
}, {
|
|
70
|
+
status: 415
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const doc = await uploadReq.payload.create({
|
|
74
|
+
collection: collectionSlug,
|
|
75
|
+
data: getUploadData(uploadReq.data),
|
|
76
|
+
file: file,
|
|
77
|
+
overrideAccess: false,
|
|
78
|
+
req: uploadReq
|
|
79
|
+
});
|
|
80
|
+
return Response.json({
|
|
81
|
+
attachment: getMediaAttachment({
|
|
82
|
+
collectionSlug,
|
|
83
|
+
doc,
|
|
84
|
+
file
|
|
85
|
+
}),
|
|
86
|
+
doc
|
|
87
|
+
});
|
|
88
|
+
} catch (err) {
|
|
89
|
+
uploadReq.payload.logger.error({
|
|
90
|
+
err,
|
|
91
|
+
msg: "AI media upload failed"
|
|
92
|
+
});
|
|
93
|
+
return Response.json({
|
|
94
|
+
error: "Media upload failed."
|
|
95
|
+
}, {
|
|
96
|
+
status: 500
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
};
|
|
@@ -61,16 +61,13 @@ export const createMentionSuggestionHandler = (options = {})=>async (req)=>{
|
|
|
61
61
|
type: "doc"
|
|
62
62
|
});
|
|
63
63
|
};
|
|
64
|
-
|
|
65
|
-
if (suggestions.length >= 5) break;
|
|
64
|
+
const searchResults = await Promise.all(collections.flatMap((collection)=>{
|
|
66
65
|
const collectionFields = collection.fields;
|
|
67
66
|
const localeCodes = query && hasLocalizedFields(collectionFields) ? getLocaleCodes(req) : [];
|
|
68
67
|
const localesToSearch = localeCodes.length > 0 ? localeCodes : [
|
|
69
68
|
null
|
|
70
69
|
];
|
|
71
|
-
|
|
72
|
-
if (suggestions.length >= 5) break;
|
|
73
|
-
const result = await req.payload.find({
|
|
70
|
+
return localesToSearch.map((locale)=>req.payload.find({
|
|
74
71
|
collection: collection.slug,
|
|
75
72
|
depth: 0,
|
|
76
73
|
limit: query ? 100 : 10,
|
|
@@ -79,16 +76,21 @@ export const createMentionSuggestionHandler = (options = {})=>async (req)=>{
|
|
|
79
76
|
} : {},
|
|
80
77
|
overrideAccess: false,
|
|
81
78
|
req
|
|
79
|
+
}).then((result)=>({
|
|
80
|
+
collection,
|
|
81
|
+
result
|
|
82
|
+
})));
|
|
83
|
+
}));
|
|
84
|
+
for (const { collection, result } of searchResults){
|
|
85
|
+
if (suggestions.length >= 5) break;
|
|
86
|
+
for (const doc of result.docs){
|
|
87
|
+
if (suggestions.length >= 5) break;
|
|
88
|
+
addSuggestion({
|
|
89
|
+
collectionSlug: collection.slug,
|
|
90
|
+
doc,
|
|
91
|
+
requireLabelMatch: Boolean(normalizedQuery),
|
|
92
|
+
useAsTitle: collection.admin?.useAsTitle
|
|
82
93
|
});
|
|
83
|
-
for (const doc of result.docs){
|
|
84
|
-
if (suggestions.length >= 5) break;
|
|
85
|
-
addSuggestion({
|
|
86
|
-
collectionSlug: collection.slug,
|
|
87
|
-
doc,
|
|
88
|
-
requireLabelMatch: Boolean(normalizedQuery),
|
|
89
|
-
useAsTitle: collection.admin?.useAsTitle
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
return Response.json({
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { verifyActionProposal } from "../ai/proposalSigning.js";
|
|
2
2
|
import { 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
6
|
import { getDefaultLocale, hasLocalizedData, isActionProposal, mergeData } from "../payload/shared.js";
|
|
6
|
-
const applyLocalizedRequiredFallback = ({ data, fallbackSource, fields })=>{
|
|
7
|
-
return mergeData(getLocalizedRequiredFallbackData({
|
|
8
|
-
fields,
|
|
9
|
-
source: fallbackSource,
|
|
10
|
-
target: data
|
|
11
|
-
}), data);
|
|
12
|
-
};
|
|
13
7
|
export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
14
8
|
if (!req.user) return Response.json({
|
|
15
9
|
error: "Unauthorized"
|
|
@@ -34,6 +28,7 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
34
28
|
status: 400
|
|
35
29
|
});
|
|
36
30
|
try {
|
|
31
|
+
const inferenceText = body?.prompt;
|
|
37
32
|
const defaultLocale = getDefaultLocale(req);
|
|
38
33
|
if (proposal.action === "updateGlobal") {
|
|
39
34
|
const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
|
|
@@ -45,7 +40,27 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
45
40
|
if (hasLocalizedData(proposal)) {
|
|
46
41
|
const beforeByLocale = {};
|
|
47
42
|
const afterByLocale = {};
|
|
48
|
-
const globalFields =
|
|
43
|
+
const globalFields = getSchemaFields({
|
|
44
|
+
fields: globalConfig.fields || [],
|
|
45
|
+
slug: proposal.slug
|
|
46
|
+
});
|
|
47
|
+
const preparedData = prepareProposalWriteData({
|
|
48
|
+
collectionConfig: {
|
|
49
|
+
fields: globalConfig.fields || [],
|
|
50
|
+
slug: proposal.slug
|
|
51
|
+
},
|
|
52
|
+
inferenceText,
|
|
53
|
+
label: proposal.label,
|
|
54
|
+
localizedData: proposal.localizedData,
|
|
55
|
+
mode: "update"
|
|
56
|
+
});
|
|
57
|
+
if (preparedData.issues.length > 0 || !preparedData.localizedData) {
|
|
58
|
+
return Response.json({
|
|
59
|
+
error: "Proposal is invalid."
|
|
60
|
+
}, {
|
|
61
|
+
status: 400
|
|
62
|
+
});
|
|
63
|
+
}
|
|
49
64
|
const defaultLocaleDoc = defaultLocale ? await req.payload.findGlobal({
|
|
50
65
|
depth: 2,
|
|
51
66
|
fallbackLocale: false,
|
|
@@ -54,8 +69,7 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
54
69
|
req,
|
|
55
70
|
slug: proposal.slug
|
|
56
71
|
}) : null;
|
|
57
|
-
|
|
58
|
-
const normalized = normalizeDataForFields(globalFields, localeData);
|
|
72
|
+
const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
|
|
59
73
|
const doc = await req.payload.findGlobal({
|
|
60
74
|
depth: 2,
|
|
61
75
|
fallbackLocale: false,
|
|
@@ -64,20 +78,43 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
64
78
|
req,
|
|
65
79
|
slug: proposal.slug
|
|
66
80
|
});
|
|
67
|
-
const completedData =
|
|
68
|
-
data: normalized.data,
|
|
81
|
+
const completedData = applyLocalizedRequiredFallbackToPreparedData({
|
|
69
82
|
fallbackSource: locale === defaultLocale ? doc : defaultLocaleDoc || doc,
|
|
70
|
-
fields: globalFields
|
|
83
|
+
fields: globalFields,
|
|
84
|
+
preparedData: localeData
|
|
71
85
|
});
|
|
86
|
+
return {
|
|
87
|
+
completedData,
|
|
88
|
+
doc,
|
|
89
|
+
locale
|
|
90
|
+
};
|
|
91
|
+
}));
|
|
92
|
+
localizedResults.forEach(({ completedData, doc, locale })=>{
|
|
72
93
|
beforeByLocale[locale] = redactSensitiveData(doc);
|
|
73
94
|
afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
|
|
74
|
-
}
|
|
95
|
+
});
|
|
75
96
|
return Response.json({
|
|
76
97
|
after: afterByLocale,
|
|
77
98
|
before: beforeByLocale
|
|
78
99
|
});
|
|
79
100
|
}
|
|
80
|
-
const
|
|
101
|
+
const preparedData = prepareProposalWriteData({
|
|
102
|
+
collectionConfig: {
|
|
103
|
+
fields: globalConfig.fields || [],
|
|
104
|
+
slug: proposal.slug
|
|
105
|
+
},
|
|
106
|
+
data: proposal.data,
|
|
107
|
+
inferenceText,
|
|
108
|
+
label: proposal.label,
|
|
109
|
+
mode: "update"
|
|
110
|
+
});
|
|
111
|
+
if (preparedData.issues.length > 0 || !preparedData.data) {
|
|
112
|
+
return Response.json({
|
|
113
|
+
error: "Proposal is invalid."
|
|
114
|
+
}, {
|
|
115
|
+
status: 400
|
|
116
|
+
});
|
|
117
|
+
}
|
|
81
118
|
const doc = await req.payload.findGlobal({
|
|
82
119
|
depth: 2,
|
|
83
120
|
...proposal.locale ? {
|
|
@@ -88,7 +125,7 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
88
125
|
slug: proposal.slug
|
|
89
126
|
});
|
|
90
127
|
return Response.json({
|
|
91
|
-
after: redactSensitiveData(mergeData(doc,
|
|
128
|
+
after: redactSensitiveData(mergeData(doc, preparedData.data)),
|
|
92
129
|
before: redactSensitiveData(doc)
|
|
93
130
|
});
|
|
94
131
|
}
|
|
@@ -119,9 +156,22 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
119
156
|
});
|
|
120
157
|
}
|
|
121
158
|
const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
|
|
122
|
-
const collectionFields =
|
|
123
|
-
const normalizeCollectionData = (data)=>normalizeAuthData(collectionConfig, normalizeDataForFields(collectionFields, data));
|
|
159
|
+
const collectionFields = getSchemaFields(collectionConfig);
|
|
124
160
|
if (hasLocalizedData(proposal)) {
|
|
161
|
+
const preparedData = prepareProposalWriteData({
|
|
162
|
+
collectionConfig,
|
|
163
|
+
inferenceText,
|
|
164
|
+
label: proposal.label,
|
|
165
|
+
localizedData: proposal.localizedData,
|
|
166
|
+
mode: proposal.action
|
|
167
|
+
});
|
|
168
|
+
if (preparedData.issues.length > 0 || !preparedData.localizedData) {
|
|
169
|
+
return Response.json({
|
|
170
|
+
error: "Proposal is invalid."
|
|
171
|
+
}, {
|
|
172
|
+
status: 400
|
|
173
|
+
});
|
|
174
|
+
}
|
|
125
175
|
const afterByLocale = {};
|
|
126
176
|
const beforeByLocale = {};
|
|
127
177
|
const defaultLocaleDoc = proposal.action === "update" && defaultLocale ? await req.payload.findByID({
|
|
@@ -134,49 +184,66 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
134
184
|
req
|
|
135
185
|
}) : null;
|
|
136
186
|
let createFallbackSource = null;
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
locale,
|
|
145
|
-
overrideAccess: false,
|
|
146
|
-
req
|
|
147
|
-
}) : defaultLocaleDoc || {};
|
|
148
|
-
const completedData = applyLocalizedRequiredFallback({
|
|
149
|
-
data: normalized.data,
|
|
150
|
-
fallbackSource,
|
|
151
|
-
fields: collectionFields
|
|
152
|
-
});
|
|
153
|
-
if (proposal.action === "create") {
|
|
187
|
+
if (proposal.action === "create") {
|
|
188
|
+
for (const [locale, localeData] of Object.entries(preparedData.localizedData)){
|
|
189
|
+
const completedData = applyLocalizedRequiredFallbackToPreparedData({
|
|
190
|
+
fallbackSource: createFallbackSource || {},
|
|
191
|
+
fields: collectionFields,
|
|
192
|
+
preparedData: localeData
|
|
193
|
+
});
|
|
154
194
|
beforeByLocale[locale] = {};
|
|
155
195
|
afterByLocale[locale] = redactSensitiveData(completedData);
|
|
156
196
|
createFallbackSource = mergeData(createFallbackSource || {}, completedData);
|
|
157
|
-
continue;
|
|
158
197
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
198
|
+
} else {
|
|
199
|
+
const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
|
|
200
|
+
const doc = await req.payload.findByID({
|
|
201
|
+
collection: proposal.collection,
|
|
202
|
+
depth: 2,
|
|
203
|
+
fallbackLocale: false,
|
|
204
|
+
id: proposal.id,
|
|
205
|
+
locale,
|
|
206
|
+
overrideAccess: false,
|
|
207
|
+
req
|
|
208
|
+
});
|
|
209
|
+
const completedData = applyLocalizedRequiredFallbackToPreparedData({
|
|
210
|
+
fallbackSource: locale === defaultLocale ? doc : defaultLocaleDoc || {},
|
|
211
|
+
fields: collectionFields,
|
|
212
|
+
preparedData: localeData
|
|
213
|
+
});
|
|
214
|
+
return {
|
|
215
|
+
completedData,
|
|
216
|
+
doc,
|
|
217
|
+
locale
|
|
218
|
+
};
|
|
219
|
+
}));
|
|
220
|
+
localizedResults.forEach(({ completedData, doc, locale })=>{
|
|
221
|
+
beforeByLocale[locale] = redactSensitiveData(doc);
|
|
222
|
+
afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
|
|
167
223
|
});
|
|
168
|
-
beforeByLocale[locale] = redactSensitiveData(doc);
|
|
169
|
-
afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
|
|
170
224
|
}
|
|
171
225
|
return Response.json({
|
|
172
226
|
after: afterByLocale,
|
|
173
227
|
before: beforeByLocale
|
|
174
228
|
});
|
|
175
229
|
}
|
|
176
|
-
const
|
|
230
|
+
const preparedData = prepareProposalWriteData({
|
|
231
|
+
collectionConfig,
|
|
232
|
+
data: proposal.data,
|
|
233
|
+
inferenceText,
|
|
234
|
+
label: proposal.label,
|
|
235
|
+
mode: proposal.action
|
|
236
|
+
});
|
|
237
|
+
if (preparedData.issues.length > 0 || !preparedData.data) {
|
|
238
|
+
return Response.json({
|
|
239
|
+
error: "Proposal is invalid."
|
|
240
|
+
}, {
|
|
241
|
+
status: 400
|
|
242
|
+
});
|
|
243
|
+
}
|
|
177
244
|
if (proposal.action === "create") {
|
|
178
245
|
return Response.json({
|
|
179
|
-
after: redactSensitiveData(
|
|
246
|
+
after: redactSensitiveData(preparedData.data),
|
|
180
247
|
before: {}
|
|
181
248
|
});
|
|
182
249
|
}
|
|
@@ -191,7 +258,7 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
191
258
|
req
|
|
192
259
|
});
|
|
193
260
|
return Response.json({
|
|
194
|
-
after: redactSensitiveData(mergeData(doc,
|
|
261
|
+
after: redactSensitiveData(mergeData(doc, preparedData.data)),
|
|
195
262
|
before: redactSensitiveData(doc)
|
|
196
263
|
});
|
|
197
264
|
} catch (err) {
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,12 @@ export type PayloadAIPluginOptions = {
|
|
|
6
6
|
collections?: CollectionPermissionMap;
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
maxOutputTokens?: number;
|
|
9
|
+
media?: {
|
|
10
|
+
acceptedMimeTypes?: string[];
|
|
11
|
+
collectionSlug?: string;
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
maxFileSize?: number;
|
|
14
|
+
};
|
|
9
15
|
models?: AIModelConfig;
|
|
10
16
|
};
|
|
11
17
|
export declare const payloadAiPlugin: (pluginOptions: PayloadAIPluginOptions) => (config: Config) => Config;
|
package/dist/index.js
CHANGED
|
@@ -2,13 +2,25 @@ import { aiProviders, getResolvedAIModelConfig } from "./ai/providerOptions.js";
|
|
|
2
2
|
import { createApplyActionHandler } from "./handlers/applyActionHandler.js";
|
|
3
3
|
import { createChatHandler } from "./handlers/chatHandler.js";
|
|
4
4
|
import { createMentionSuggestionHandler } from "./handlers/mentionSuggestionHandler.js";
|
|
5
|
+
import { createMediaUploadHandler } from "./handlers/mediaUploadHandler.js";
|
|
5
6
|
import { createProposalDiffHandler } from "./handlers/proposalDiffHandler.js";
|
|
6
7
|
import { createAuditLogHandler } from "./handlers/auditLogHandler.js";
|
|
7
8
|
import { resolveCollectionPermissions } from "./payload/collectionPermissions.js";
|
|
8
9
|
import { isInternalCollection } from "./payload/shared.js";
|
|
9
|
-
const
|
|
10
|
+
const resolveMediaUploadOptions = (media)=>{
|
|
11
|
+
if (!media || media.enabled === false) return null;
|
|
12
|
+
return {
|
|
13
|
+
...media.acceptedMimeTypes ? {
|
|
14
|
+
acceptedMimeTypes: media.acceptedMimeTypes
|
|
15
|
+
} : {},
|
|
16
|
+
collectionSlug: media.collectionSlug || "media",
|
|
17
|
+
...typeof media.maxFileSize === "number" && Number.isFinite(media.maxFileSize) && media.maxFileSize > 0 ? {
|
|
18
|
+
maxFileSize: Math.floor(media.maxFileSize)
|
|
19
|
+
} : {}
|
|
20
|
+
};
|
|
21
|
+
};
|
|
10
22
|
const createAIChangesCollection = ()=>({
|
|
11
|
-
slug:
|
|
23
|
+
slug: "payload-ai-auditlog",
|
|
12
24
|
access: {
|
|
13
25
|
create: ()=>false,
|
|
14
26
|
delete: ()=>false,
|
|
@@ -156,9 +168,10 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
156
168
|
const collectionPermissions = resolveCollectionPermissions(pluginOptions.collections);
|
|
157
169
|
const allowUserApiKeys = pluginOptions.allowUserApiKeys !== false;
|
|
158
170
|
const modelConfig = getResolvedAIModelConfig(pluginOptions.models);
|
|
171
|
+
const mediaUploadOptions = resolveMediaUploadOptions(pluginOptions.media);
|
|
159
172
|
const maxOutputTokens = typeof pluginOptions.maxOutputTokens === "number" && Number.isFinite(pluginOptions.maxOutputTokens) && pluginOptions.maxOutputTokens > 0 ? Math.floor(pluginOptions.maxOutputTokens) : undefined;
|
|
160
173
|
if (!config.collections) config.collections = [];
|
|
161
|
-
if (!config.collections.some((collection)=>collection.slug ===
|
|
174
|
+
if (!config.collections.some((collection)=>collection.slug === "payload-ai-auditlog")) {
|
|
162
175
|
config.collections.push(createAIChangesCollection());
|
|
163
176
|
}
|
|
164
177
|
addAccountFields({
|
|
@@ -166,7 +179,13 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
166
179
|
config
|
|
167
180
|
});
|
|
168
181
|
if (pluginOptions.disabled) return config;
|
|
169
|
-
const mentionCollectionSlugs = config.collections.
|
|
182
|
+
const mentionCollectionSlugs = config.collections.flatMap((collection)=>{
|
|
183
|
+
if (isInternalCollection(collection.slug)) return [];
|
|
184
|
+
if (collectionPermissions && !collectionPermissions[collection.slug]?.read) return [];
|
|
185
|
+
return [
|
|
186
|
+
collection.slug
|
|
187
|
+
];
|
|
188
|
+
});
|
|
170
189
|
if (!config.endpoints) config.endpoints = [];
|
|
171
190
|
if (!config.admin) config.admin = {};
|
|
172
191
|
config.admin.custom = {
|
|
@@ -175,12 +194,18 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
175
194
|
...config.admin.custom?.payloadAiPlugin || {},
|
|
176
195
|
collectionSlugs: mentionCollectionSlugs,
|
|
177
196
|
allowUserApiKeys,
|
|
197
|
+
...mediaUploadOptions ? {
|
|
198
|
+
media: {
|
|
199
|
+
...mediaUploadOptions,
|
|
200
|
+
enabled: true
|
|
201
|
+
}
|
|
202
|
+
} : {},
|
|
178
203
|
models: modelConfig
|
|
179
204
|
}
|
|
180
205
|
};
|
|
181
206
|
if (!config.admin.components) config.admin.components = {};
|
|
182
207
|
if (!config.admin.components.beforeDashboard) config.admin.components.beforeDashboard = [];
|
|
183
|
-
config.admin.components.beforeDashboard.push(`@mvriu5/payload-ai/client#
|
|
208
|
+
config.admin.components.beforeDashboard.push(`@mvriu5/payload-ai/client#Dashboard`);
|
|
184
209
|
config.endpoints.push({
|
|
185
210
|
handler: createChatHandler({
|
|
186
211
|
allowUserApiKeys,
|
|
@@ -193,7 +218,7 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
193
218
|
});
|
|
194
219
|
config.endpoints.push({
|
|
195
220
|
handler: createApplyActionHandler({
|
|
196
|
-
changeLogCollection:
|
|
221
|
+
changeLogCollection: "payload-ai-auditlog",
|
|
197
222
|
collections: collectionPermissions
|
|
198
223
|
}),
|
|
199
224
|
method: "post",
|
|
@@ -201,7 +226,7 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
201
226
|
});
|
|
202
227
|
config.endpoints.push({
|
|
203
228
|
handler: createAuditLogHandler({
|
|
204
|
-
changeLogCollection:
|
|
229
|
+
changeLogCollection: "payload-ai-auditlog"
|
|
205
230
|
}),
|
|
206
231
|
method: "get",
|
|
207
232
|
path: "/ai-audit-log"
|
|
@@ -220,6 +245,13 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
220
245
|
method: "post",
|
|
221
246
|
path: "/ai-mention-suggestion"
|
|
222
247
|
});
|
|
248
|
+
if (mediaUploadOptions) {
|
|
249
|
+
config.endpoints.push({
|
|
250
|
+
handler: createMediaUploadHandler(mediaUploadOptions),
|
|
251
|
+
method: "post",
|
|
252
|
+
path: "/ai-upload-media"
|
|
253
|
+
});
|
|
254
|
+
}
|
|
223
255
|
if (incomingOnInit) {
|
|
224
256
|
config.onInit = async (payload)=>{
|
|
225
257
|
await incomingOnInit(payload);
|
|
@@ -6,7 +6,9 @@ const allActions = [
|
|
|
6
6
|
"update"
|
|
7
7
|
];
|
|
8
8
|
const getKnownCollectionSlugs = (req)=>{
|
|
9
|
-
return req.payload.config.collections.
|
|
9
|
+
return req.payload.config.collections.flatMap((collection)=>isInternalCollection(collection.slug) ? [] : [
|
|
10
|
+
collection.slug
|
|
11
|
+
]);
|
|
10
12
|
};
|
|
11
13
|
export const resolveCollectionPermissions = (collections)=>{
|
|
12
14
|
if (!collections) return undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PayloadHandler } from "payload";
|
|
2
|
+
type LogLevel = "error" | "info" | "warn";
|
|
3
|
+
type LogEntry = {
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
msg: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const getLogPreview: (value?: string | null) => string | null;
|
|
8
|
+
export declare const logHandlerEvent: (req: Parameters<PayloadHandler>[0], level: LogLevel, entry: LogEntry) => void;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const maxPreviewLength = 180;
|
|
2
|
+
export const getLogPreview = (value)=>{
|
|
3
|
+
if (!value) return null;
|
|
4
|
+
const trimmed = value.trim();
|
|
5
|
+
if (!trimmed) return null;
|
|
6
|
+
if (trimmed.length <= maxPreviewLength) return trimmed;
|
|
7
|
+
return `${trimmed.slice(0, maxPreviewLength).trim()}...`;
|
|
8
|
+
};
|
|
9
|
+
export const logHandlerEvent = (req, level, entry)=>{
|
|
10
|
+
const logger = req.payload?.logger;
|
|
11
|
+
const log = logger?.[level];
|
|
12
|
+
if (typeof log !== "function") return;
|
|
13
|
+
log.call(logger, entry);
|
|
14
|
+
};
|
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
export type FieldConfig = {
|
|
2
|
-
|
|
2
|
+
admin?: Record<string, unknown>;
|
|
3
|
+
blocks?: readonly BlockConfig[];
|
|
3
4
|
defaultValue?: unknown;
|
|
4
|
-
fields?: FieldConfig[];
|
|
5
|
+
fields?: readonly FieldConfig[];
|
|
6
|
+
hasMany?: boolean;
|
|
5
7
|
label?: unknown;
|
|
6
8
|
localized?: boolean;
|
|
7
9
|
name?: string;
|
|
8
|
-
options?: (string | {
|
|
10
|
+
options?: readonly (string | {
|
|
9
11
|
value?: string;
|
|
10
12
|
})[];
|
|
13
|
+
relationTo?: unknown;
|
|
11
14
|
required?: boolean;
|
|
12
15
|
type?: string;
|
|
13
16
|
};
|
|
14
17
|
type BlockConfig = {
|
|
15
|
-
fields?: FieldConfig[];
|
|
18
|
+
fields?: readonly FieldConfig[];
|
|
16
19
|
slug: string;
|
|
17
20
|
};
|
|
18
21
|
export type CollectionConfig = {
|
|
22
|
+
admin?: Record<string, unknown>;
|
|
19
23
|
auth?: unknown;
|
|
20
|
-
fields
|
|
24
|
+
fields?: readonly FieldConfig[];
|
|
21
25
|
slug: string;
|
|
26
|
+
versions?: boolean | {
|
|
27
|
+
drafts?: boolean | Record<string, unknown>;
|
|
28
|
+
};
|
|
22
29
|
};
|
|
23
30
|
export type NormalizedData = {
|
|
24
31
|
coercedFields: string[];
|
|
@@ -26,12 +33,9 @@ export type NormalizedData = {
|
|
|
26
33
|
droppedFields: string[];
|
|
27
34
|
};
|
|
28
35
|
export declare const isAuthCollection: (collectionConfig?: CollectionConfig | null) => boolean;
|
|
29
|
-
export declare const
|
|
36
|
+
export declare const hasDrafts: (collectionConfig?: CollectionConfig | null) => boolean;
|
|
37
|
+
export declare const getSchemaFields: (collectionConfig?: CollectionConfig | null) => FieldConfig[];
|
|
38
|
+
export declare const createLexicalText: (value: unknown) => Record<string, unknown>;
|
|
30
39
|
export declare const normalizeAuthData: (collectionConfig: CollectionConfig | undefined, normalized: NormalizedData) => NormalizedData;
|
|
31
|
-
export declare const normalizeDataForFields: (fields: FieldConfig[], data: Record<string, unknown>) => NormalizedData;
|
|
32
|
-
export declare const getLocalizedRequiredFallbackData: ({ fields, source, target, }: {
|
|
33
|
-
fields: FieldConfig[];
|
|
34
|
-
source: Record<string, unknown>;
|
|
35
|
-
target: Record<string, unknown>;
|
|
36
|
-
}) => Record<string, unknown>;
|
|
40
|
+
export declare const normalizeDataForFields: (fields: readonly FieldConfig[], data: Record<string, unknown>) => NormalizedData;
|
|
37
41
|
export {};
|