@mvriu5/payload-ai 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -13
- package/dist/ai/providerOptions.d.ts +24 -1
- package/dist/ai/providerOptions.js +81 -0
- package/dist/ai/providerRuntime.d.ts +2 -1
- package/dist/ai/providerRuntime.js +5 -2
- package/dist/ai/tokenUsage.d.ts +38 -0
- package/dist/ai/tokenUsage.js +106 -0
- package/dist/components/Icons.d.ts +1 -8
- package/dist/components/Icons.js +10 -207
- package/dist/components/{ActionToast.d.ts → action-toast/ActionToast.d.ts} +5 -2
- package/dist/components/{ActionToast.js → action-toast/ActionToast.js} +81 -54
- package/dist/components/{ActionToast.module.css → action-toast/ActionToast.module.css} +0 -79
- package/dist/components/ai-input/AIInput.d.ts +5 -0
- package/dist/components/ai-input/AIInput.js +548 -0
- package/dist/components/{AIInput.module.css → ai-input/AIInput.module.css} +150 -44
- 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} +56 -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 +17 -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 +50 -0
- package/dist/components/hooks/useAIChatStream.js +226 -0
- package/dist/components/hooks/useAISettings.d.ts +6 -4
- package/dist/components/hooks/useAISettings.js +68 -27
- 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 +40 -0
- package/dist/components/hooks/usePluginConfig.js +27 -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/components/text-shimmer/TextShimmer.d.ts +9 -0
- package/dist/components/text-shimmer/TextShimmer.js +34 -0
- package/dist/components/text-shimmer/TextShimmer.module.css +19 -0
- package/dist/exports/client.d.ts +4 -2
- package/dist/exports/client.js +4 -2
- package/dist/handlers/applyActionHandler.js +27 -7
- package/dist/handlers/chatHandler.d.ts +4 -1
- package/dist/handlers/chatHandler.js +499 -74
- 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 +41 -29
- package/dist/index.d.ts +12 -1
- package/dist/index.js +131 -12
- package/dist/payload/collectionPermissions.js +3 -1
- package/dist/payload/normalizeData.d.ts +0 -6
- package/dist/payload/normalizeData.js +25 -13
- package/dist/payload/proposalData.js +4 -1
- package/dist/payload/schemaContext.js +98 -43
- package/package.json +22 -20
- package/dist/components/AIInput.d.ts +0 -2
- package/dist/components/AIInput.js +0 -896
|
@@ -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({
|
|
@@ -69,7 +69,7 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
69
69
|
req,
|
|
70
70
|
slug: proposal.slug
|
|
71
71
|
}) : null;
|
|
72
|
-
|
|
72
|
+
const localizedResults = await Promise.all(Object.entries(preparedData.localizedData).map(async ([locale, localeData])=>{
|
|
73
73
|
const doc = await req.payload.findGlobal({
|
|
74
74
|
depth: 2,
|
|
75
75
|
fallbackLocale: false,
|
|
@@ -83,9 +83,16 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
83
83
|
fields: globalFields,
|
|
84
84
|
preparedData: localeData
|
|
85
85
|
});
|
|
86
|
+
return {
|
|
87
|
+
completedData,
|
|
88
|
+
doc,
|
|
89
|
+
locale
|
|
90
|
+
};
|
|
91
|
+
}));
|
|
92
|
+
localizedResults.forEach(({ completedData, doc, locale })=>{
|
|
86
93
|
beforeByLocale[locale] = redactSensitiveData(doc);
|
|
87
94
|
afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
|
|
88
|
-
}
|
|
95
|
+
});
|
|
89
96
|
return Response.json({
|
|
90
97
|
after: afterByLocale,
|
|
91
98
|
before: beforeByLocale
|
|
@@ -177,38 +184,43 @@ export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
|
177
184
|
req
|
|
178
185
|
}) : null;
|
|
179
186
|
let createFallbackSource = null;
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
overrideAccess: false,
|
|
188
|
-
req
|
|
189
|
-
}) : defaultLocaleDoc || {};
|
|
190
|
-
const completedData = applyLocalizedRequiredFallbackToPreparedData({
|
|
191
|
-
fallbackSource,
|
|
192
|
-
fields: collectionFields,
|
|
193
|
-
preparedData: localeData
|
|
194
|
-
});
|
|
195
|
-
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
|
+
});
|
|
196
194
|
beforeByLocale[locale] = {};
|
|
197
195
|
afterByLocale[locale] = redactSensitiveData(completedData);
|
|
198
196
|
createFallbackSource = mergeData(createFallbackSource || {}, completedData);
|
|
199
|
-
continue;
|
|
200
197
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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));
|
|
209
223
|
});
|
|
210
|
-
beforeByLocale[locale] = redactSensitiveData(doc);
|
|
211
|
-
afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
|
|
212
224
|
}
|
|
213
225
|
return Response.json({
|
|
214
226
|
after: afterByLocale,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import type { Config } from "payload";
|
|
2
|
-
import { type AIModelConfig } from "./ai/providerOptions.js";
|
|
2
|
+
import { type AIModelConfig, type AIProviderConfig } from "./ai/providerOptions.js";
|
|
3
|
+
import { type MaxTokenUsageOptions } from "./ai/tokenUsage.js";
|
|
3
4
|
import { type CollectionPermissionMap } from "./payload/collectionPermissions.js";
|
|
5
|
+
export type { AIModelConfig, AIProviderConfig, AIProviderModelOption } from "./ai/providerOptions.js";
|
|
6
|
+
export type { MaxTokenUsageOptions } from "./ai/tokenUsage.js";
|
|
4
7
|
export type PayloadAIPluginOptions = {
|
|
5
8
|
allowUserApiKeys?: boolean;
|
|
6
9
|
collections?: CollectionPermissionMap;
|
|
7
10
|
disabled?: boolean;
|
|
8
11
|
maxOutputTokens?: number;
|
|
12
|
+
media?: {
|
|
13
|
+
acceptedMimeTypes?: string[];
|
|
14
|
+
collectionSlug?: string;
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
maxFileSize?: number;
|
|
17
|
+
};
|
|
9
18
|
models?: AIModelConfig;
|
|
19
|
+
maxTokenUsage?: MaxTokenUsageOptions;
|
|
20
|
+
providers?: AIProviderConfig[];
|
|
10
21
|
};
|
|
11
22
|
export declare const payloadAiPlugin: (pluginOptions: PayloadAIPluginOptions) => (config: Config) => Config;
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
import { aiProviders, getResolvedAIModelConfig } from "./ai/providerOptions.js";
|
|
1
|
+
import { aiProviders, getResolvedAIModelConfig, resolveAIProviderConfigs, toClientAIProviderProfiles } from "./ai/providerOptions.js";
|
|
2
|
+
import { resolveMaxTokenUsageOptions, tokenUsageCollectionSlug } from "./ai/tokenUsage.js";
|
|
2
3
|
import { createApplyActionHandler } from "./handlers/applyActionHandler.js";
|
|
3
4
|
import { createChatHandler } from "./handlers/chatHandler.js";
|
|
4
5
|
import { createMentionSuggestionHandler } from "./handlers/mentionSuggestionHandler.js";
|
|
6
|
+
import { createMediaUploadHandler } from "./handlers/mediaUploadHandler.js";
|
|
5
7
|
import { createProposalDiffHandler } from "./handlers/proposalDiffHandler.js";
|
|
6
8
|
import { createAuditLogHandler } from "./handlers/auditLogHandler.js";
|
|
7
9
|
import { resolveCollectionPermissions } from "./payload/collectionPermissions.js";
|
|
8
10
|
import { isInternalCollection } from "./payload/shared.js";
|
|
9
|
-
const
|
|
11
|
+
const resolveMediaUploadOptions = (media)=>{
|
|
12
|
+
if (!media || media.enabled === false) return null;
|
|
13
|
+
return {
|
|
14
|
+
...media.acceptedMimeTypes ? {
|
|
15
|
+
acceptedMimeTypes: media.acceptedMimeTypes
|
|
16
|
+
} : {},
|
|
17
|
+
collectionSlug: media.collectionSlug || "media",
|
|
18
|
+
...typeof media.maxFileSize === "number" && Number.isFinite(media.maxFileSize) && media.maxFileSize > 0 ? {
|
|
19
|
+
maxFileSize: Math.floor(media.maxFileSize)
|
|
20
|
+
} : {}
|
|
21
|
+
};
|
|
22
|
+
};
|
|
10
23
|
const createAIChangesCollection = ()=>({
|
|
11
|
-
slug:
|
|
24
|
+
slug: "payload-ai-auditlog",
|
|
12
25
|
access: {
|
|
13
26
|
create: ()=>false,
|
|
14
27
|
delete: ()=>false,
|
|
@@ -125,6 +138,56 @@ const createAIChangesCollection = ()=>({
|
|
|
125
138
|
],
|
|
126
139
|
timestamps: true
|
|
127
140
|
});
|
|
141
|
+
const createAITokenUsageCollection = ()=>({
|
|
142
|
+
slug: tokenUsageCollectionSlug,
|
|
143
|
+
access: {
|
|
144
|
+
create: ()=>false,
|
|
145
|
+
delete: ()=>false,
|
|
146
|
+
read: ()=>false,
|
|
147
|
+
update: ()=>false
|
|
148
|
+
},
|
|
149
|
+
admin: {
|
|
150
|
+
hidden: true
|
|
151
|
+
},
|
|
152
|
+
fields: [
|
|
153
|
+
{
|
|
154
|
+
name: "userID",
|
|
155
|
+
type: "text",
|
|
156
|
+
index: true,
|
|
157
|
+
required: true
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "provider",
|
|
161
|
+
type: "text",
|
|
162
|
+
required: true
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "model",
|
|
166
|
+
type: "text",
|
|
167
|
+
required: true
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "inputTokens",
|
|
171
|
+
type: "number"
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "outputTokens",
|
|
175
|
+
type: "number"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "totalTokens",
|
|
179
|
+
type: "number",
|
|
180
|
+
required: true
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "recordedAt",
|
|
184
|
+
type: "date",
|
|
185
|
+
index: true,
|
|
186
|
+
required: true
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
timestamps: true
|
|
190
|
+
});
|
|
128
191
|
const addAccountFields = ({ allowUserApiKeys, config })=>{
|
|
129
192
|
const adminUserSlug = config.admin?.user;
|
|
130
193
|
if (!adminUserSlug || !config.collections) return;
|
|
@@ -151,22 +214,60 @@ const addAccountFields = ({ allowUserApiKeys, config })=>{
|
|
|
151
214
|
});
|
|
152
215
|
}
|
|
153
216
|
};
|
|
217
|
+
const aiField = {
|
|
218
|
+
name: "payloadAi",
|
|
219
|
+
type: "ui",
|
|
220
|
+
admin: {
|
|
221
|
+
components: {
|
|
222
|
+
Field: "@mvriu5/payload-ai/client#AIInput"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const addAIFieldToDocumentsAndGlobals = (config)=>{
|
|
227
|
+
for (const collection of config.collections || []){
|
|
228
|
+
if (isInternalCollection(collection.slug)) continue;
|
|
229
|
+
if (collection.slug === "payload-ai-auditlog") continue;
|
|
230
|
+
collection.fields = [
|
|
231
|
+
aiField,
|
|
232
|
+
...collection.fields || []
|
|
233
|
+
];
|
|
234
|
+
}
|
|
235
|
+
for (const global of config.globals || []){
|
|
236
|
+
global.fields = [
|
|
237
|
+
aiField,
|
|
238
|
+
...global.fields || []
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
};
|
|
154
242
|
export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
155
243
|
const incomingOnInit = config.onInit;
|
|
156
244
|
const collectionPermissions = resolveCollectionPermissions(pluginOptions.collections);
|
|
157
|
-
const
|
|
245
|
+
const providerConfigs = resolveAIProviderConfigs(pluginOptions.providers);
|
|
246
|
+
const managedProviders = providerConfigs.length > 0;
|
|
247
|
+
const allowUserApiKeys = !managedProviders && pluginOptions.allowUserApiKeys !== false;
|
|
158
248
|
const modelConfig = getResolvedAIModelConfig(pluginOptions.models);
|
|
249
|
+
const maxTokenUsage = resolveMaxTokenUsageOptions(pluginOptions.maxTokenUsage);
|
|
250
|
+
const mediaUploadOptions = resolveMediaUploadOptions(pluginOptions.media);
|
|
159
251
|
const maxOutputTokens = typeof pluginOptions.maxOutputTokens === "number" && Number.isFinite(pluginOptions.maxOutputTokens) && pluginOptions.maxOutputTokens > 0 ? Math.floor(pluginOptions.maxOutputTokens) : undefined;
|
|
160
252
|
if (!config.collections) config.collections = [];
|
|
161
|
-
if (!config.collections.some((collection)=>collection.slug ===
|
|
253
|
+
if (!config.collections.some((collection)=>collection.slug === "payload-ai-auditlog")) {
|
|
162
254
|
config.collections.push(createAIChangesCollection());
|
|
163
255
|
}
|
|
164
|
-
|
|
256
|
+
if (maxTokenUsage && !config.collections.some((collection)=>collection.slug === tokenUsageCollectionSlug)) {
|
|
257
|
+
config.collections.push(createAITokenUsageCollection());
|
|
258
|
+
}
|
|
259
|
+
if (!managedProviders) addAccountFields({
|
|
165
260
|
allowUserApiKeys,
|
|
166
261
|
config
|
|
167
262
|
});
|
|
168
263
|
if (pluginOptions.disabled) return config;
|
|
169
|
-
const mentionCollectionSlugs = config.collections.
|
|
264
|
+
const mentionCollectionSlugs = config.collections.flatMap((collection)=>{
|
|
265
|
+
if (isInternalCollection(collection.slug)) return [];
|
|
266
|
+
if (collectionPermissions && !collectionPermissions[collection.slug]?.read) return [];
|
|
267
|
+
return [
|
|
268
|
+
collection.slug
|
|
269
|
+
];
|
|
270
|
+
});
|
|
170
271
|
if (!config.endpoints) config.endpoints = [];
|
|
171
272
|
if (!config.admin) config.admin = {};
|
|
172
273
|
config.admin.custom = {
|
|
@@ -175,25 +276,35 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
175
276
|
...config.admin.custom?.payloadAiPlugin || {},
|
|
176
277
|
collectionSlugs: mentionCollectionSlugs,
|
|
177
278
|
allowUserApiKeys,
|
|
178
|
-
|
|
279
|
+
managedProviders,
|
|
280
|
+
...mediaUploadOptions ? {
|
|
281
|
+
media: {
|
|
282
|
+
...mediaUploadOptions,
|
|
283
|
+
enabled: true
|
|
284
|
+
}
|
|
285
|
+
} : {},
|
|
286
|
+
models: modelConfig,
|
|
287
|
+
providers: toClientAIProviderProfiles(providerConfigs)
|
|
179
288
|
}
|
|
180
289
|
};
|
|
181
290
|
if (!config.admin.components) config.admin.components = {};
|
|
182
291
|
if (!config.admin.components.beforeDashboard) config.admin.components.beforeDashboard = [];
|
|
183
|
-
config.admin.components.beforeDashboard.push(`@mvriu5/payload-ai/client#
|
|
292
|
+
config.admin.components.beforeDashboard.push(`@mvriu5/payload-ai/client#Dashboard`);
|
|
184
293
|
config.endpoints.push({
|
|
185
294
|
handler: createChatHandler({
|
|
186
295
|
allowUserApiKeys,
|
|
187
296
|
collections: collectionPermissions,
|
|
188
297
|
maxOutputTokens,
|
|
189
|
-
|
|
298
|
+
maxTokenUsage,
|
|
299
|
+
models: modelConfig,
|
|
300
|
+
providers: providerConfigs
|
|
190
301
|
}),
|
|
191
302
|
method: "post",
|
|
192
303
|
path: "/ai-chat"
|
|
193
304
|
});
|
|
194
305
|
config.endpoints.push({
|
|
195
306
|
handler: createApplyActionHandler({
|
|
196
|
-
changeLogCollection:
|
|
307
|
+
changeLogCollection: "payload-ai-auditlog",
|
|
197
308
|
collections: collectionPermissions
|
|
198
309
|
}),
|
|
199
310
|
method: "post",
|
|
@@ -201,7 +312,7 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
201
312
|
});
|
|
202
313
|
config.endpoints.push({
|
|
203
314
|
handler: createAuditLogHandler({
|
|
204
|
-
changeLogCollection:
|
|
315
|
+
changeLogCollection: "payload-ai-auditlog"
|
|
205
316
|
}),
|
|
206
317
|
method: "get",
|
|
207
318
|
path: "/ai-audit-log"
|
|
@@ -220,10 +331,18 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
220
331
|
method: "post",
|
|
221
332
|
path: "/ai-mention-suggestion"
|
|
222
333
|
});
|
|
334
|
+
if (mediaUploadOptions) {
|
|
335
|
+
config.endpoints.push({
|
|
336
|
+
handler: createMediaUploadHandler(mediaUploadOptions),
|
|
337
|
+
method: "post",
|
|
338
|
+
path: "/ai-upload-media"
|
|
339
|
+
});
|
|
340
|
+
}
|
|
223
341
|
if (incomingOnInit) {
|
|
224
342
|
config.onInit = async (payload)=>{
|
|
225
343
|
await incomingOnInit(payload);
|
|
226
344
|
};
|
|
227
345
|
}
|
|
346
|
+
addAIFieldToDocumentsAndGlobals(config);
|
|
228
347
|
return config;
|
|
229
348
|
};
|
|
@@ -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;
|
|
@@ -33,15 +33,9 @@ export type NormalizedData = {
|
|
|
33
33
|
droppedFields: string[];
|
|
34
34
|
};
|
|
35
35
|
export declare const isAuthCollection: (collectionConfig?: CollectionConfig | null) => boolean;
|
|
36
|
-
export declare const getCollectionFields: (collectionConfig?: CollectionConfig | null) => FieldConfig[];
|
|
37
36
|
export declare const hasDrafts: (collectionConfig?: CollectionConfig | null) => boolean;
|
|
38
37
|
export declare const getSchemaFields: (collectionConfig?: CollectionConfig | null) => FieldConfig[];
|
|
39
38
|
export declare const createLexicalText: (value: unknown) => Record<string, unknown>;
|
|
40
39
|
export declare const normalizeAuthData: (collectionConfig: CollectionConfig | undefined, normalized: NormalizedData) => NormalizedData;
|
|
41
40
|
export declare const normalizeDataForFields: (fields: readonly FieldConfig[], data: Record<string, unknown>) => NormalizedData;
|
|
42
|
-
export declare const getLocalizedRequiredFallbackData: ({ fields, source, target, }: {
|
|
43
|
-
fields: readonly FieldConfig[];
|
|
44
|
-
source: Record<string, unknown>;
|
|
45
|
-
target: Record<string, unknown>;
|
|
46
|
-
}) => Record<string, unknown>;
|
|
47
41
|
export {};
|
|
@@ -6,7 +6,7 @@ const getNamedFields = (fields)=>{
|
|
|
6
6
|
export const isAuthCollection = (collectionConfig)=>{
|
|
7
7
|
return Boolean(collectionConfig?.auth);
|
|
8
8
|
};
|
|
9
|
-
|
|
9
|
+
const getCollectionFields = (collectionConfig)=>{
|
|
10
10
|
const fields = [
|
|
11
11
|
...collectionConfig?.fields || []
|
|
12
12
|
];
|
|
@@ -45,7 +45,12 @@ export const getSchemaFields = (collectionConfig)=>{
|
|
|
45
45
|
export const createLexicalText = (value)=>{
|
|
46
46
|
if (isRecord(value) && isRecord(value.root)) return value;
|
|
47
47
|
const text = Array.isArray(value) ? value.join("\n") : String(value || "");
|
|
48
|
-
const lines = text.split("\n").
|
|
48
|
+
const lines = text.split("\n").flatMap((line)=>{
|
|
49
|
+
const trimmedLine = line.trim();
|
|
50
|
+
return trimmedLine ? [
|
|
51
|
+
trimmedLine
|
|
52
|
+
] : [];
|
|
53
|
+
});
|
|
49
54
|
return {
|
|
50
55
|
root: {
|
|
51
56
|
children: (lines.length ? lines : [
|
|
@@ -89,7 +94,12 @@ const normalizeArrayValue = (field, value)=>{
|
|
|
89
94
|
});
|
|
90
95
|
};
|
|
91
96
|
const getOptionValues = (field)=>{
|
|
92
|
-
return (field.options || []).
|
|
97
|
+
return (field.options || []).flatMap((option)=>{
|
|
98
|
+
const value = typeof option === "string" ? option : option.value;
|
|
99
|
+
return value ? [
|
|
100
|
+
value
|
|
101
|
+
] : [];
|
|
102
|
+
});
|
|
93
103
|
};
|
|
94
104
|
const normalizeOptionValue = (field, value)=>{
|
|
95
105
|
const optionValues = getOptionValues(field);
|
|
@@ -101,19 +111,21 @@ const normalizeOptionValue = (field, value)=>{
|
|
|
101
111
|
};
|
|
102
112
|
const normalizeBlocksValue = (field, value)=>{
|
|
103
113
|
if (!Array.isArray(value)) return value;
|
|
104
|
-
return value.
|
|
105
|
-
if (!isRecord(item)) return
|
|
114
|
+
return value.flatMap((item)=>{
|
|
115
|
+
if (!isRecord(item)) return [];
|
|
106
116
|
const blockType = typeof item.blockType === "string" ? item.blockType : typeof item.type === "string" ? item.type : typeof item.slug === "string" ? item.slug : null;
|
|
107
|
-
if (!blockType) return
|
|
117
|
+
if (!blockType) return [];
|
|
108
118
|
const block = field.blocks?.find((candidate)=>candidate.slug === blockType);
|
|
109
|
-
if (!block) return
|
|
119
|
+
if (!block) return [];
|
|
110
120
|
const { blockType: _blockType, type: _type, slug: _slug, ...data } = item;
|
|
111
121
|
const normalizedBlock = normalizeDataForFields(block.fields || [], data).data;
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
return [
|
|
123
|
+
{
|
|
124
|
+
...normalizedBlock,
|
|
125
|
+
blockType
|
|
126
|
+
}
|
|
127
|
+
];
|
|
128
|
+
});
|
|
117
129
|
};
|
|
118
130
|
const normalizeFieldValue = (field, value)=>{
|
|
119
131
|
if (value === undefined) return value;
|
|
@@ -209,7 +221,7 @@ export const normalizeDataForFields = (fields, data)=>{
|
|
|
209
221
|
droppedFields
|
|
210
222
|
};
|
|
211
223
|
};
|
|
212
|
-
|
|
224
|
+
const getLocalizedRequiredFallbackData = ({ fields, source, target })=>{
|
|
213
225
|
const fallbackData = {};
|
|
214
226
|
for (const field of getNamedFields(fields)){
|
|
215
227
|
const targetValue = target[field.name];
|
|
@@ -69,7 +69,10 @@ const normalizeRelationshipScalar = (value)=>{
|
|
|
69
69
|
}
|
|
70
70
|
const trimmedValue = value.trim();
|
|
71
71
|
if (!trimmedValue || /\s/.test(trimmedValue)) return undefined;
|
|
72
|
-
if (/^\d+$/.test(trimmedValue)
|
|
72
|
+
if (/^\d+$/.test(trimmedValue)) {
|
|
73
|
+
const numericValue = Number(trimmedValue);
|
|
74
|
+
return numericValue > 0 ? numericValue : undefined;
|
|
75
|
+
}
|
|
73
76
|
return trimmedValue;
|
|
74
77
|
};
|
|
75
78
|
const normalizeRelationshipValue = ({ field, issues, path, value })=>{
|