@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.
Files changed (55) hide show
  1. package/README.md +42 -0
  2. package/dist/components/Icons.d.ts +0 -8
  3. package/dist/components/Icons.js +4 -216
  4. package/dist/components/{ActionToast.d.ts → action-toast/ActionToast.d.ts} +2 -2
  5. package/dist/components/{ActionToast.js → action-toast/ActionToast.js} +27 -35
  6. package/dist/components/{ActionToast.module.css → action-toast/ActionToast.module.css} +0 -79
  7. package/dist/components/ai-input/AIInput.js +433 -0
  8. package/dist/components/{AIInput.module.css → ai-input/AIInput.module.css} +119 -42
  9. package/dist/components/ai-input/badge.d.ts +14 -0
  10. package/dist/components/ai-input/badge.js +85 -0
  11. package/dist/components/{AuditLogList.d.ts → audit-log-list/AuditLogList.d.ts} +4 -8
  12. package/dist/components/{AuditLogList.js → audit-log-list/AuditLogList.js} +49 -21
  13. package/dist/components/{AuditLogList.module.css → audit-log-list/AuditLogList.module.css} +11 -65
  14. package/dist/components/dashboard/Dashboard.d.ts +2 -0
  15. package/dist/components/dashboard/Dashboard.js +15 -0
  16. package/dist/components/dashboard/Dashboard.module.css +13 -0
  17. package/dist/components/{DiffDialog.d.ts → diff-dialog/DiffDialog.d.ts} +2 -2
  18. package/dist/components/{DiffDialog.js → diff-dialog/DiffDialog.js} +200 -189
  19. package/dist/components/{DiffDialog.module.css → diff-dialog/DiffDialog.module.css} +17 -35
  20. package/dist/components/hooks/useAIChatStream.d.ts +39 -0
  21. package/dist/components/hooks/useAIChatStream.js +217 -0
  22. package/dist/components/hooks/useAISettings.js +26 -25
  23. package/dist/components/hooks/useAuditLog.d.ts +11 -0
  24. package/dist/components/hooks/useAuditLog.js +41 -0
  25. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +1 -1
  26. package/dist/components/hooks/useDocumentMentionSuggestions.js +31 -27
  27. package/dist/components/hooks/useMentions.d.ts +58 -0
  28. package/dist/components/hooks/useMentions.js +276 -0
  29. package/dist/components/hooks/usePluginConfig.d.ts +52 -0
  30. package/dist/components/hooks/usePluginConfig.js +20 -0
  31. package/dist/components/{MentionPopover.d.ts → mention-popover/MentionPopover.d.ts} +2 -4
  32. package/dist/components/{MentionPopover.js → mention-popover/MentionPopover.js} +1 -8
  33. package/dist/components/{MentionPopover.module.css → mention-popover/MentionPopover.module.css} +1 -1
  34. package/dist/exports/client.d.ts +2 -2
  35. package/dist/exports/client.js +2 -2
  36. package/dist/handlers/applyActionHandler.js +341 -112
  37. package/dist/handlers/chatHandler.js +940 -84
  38. package/dist/handlers/mediaUploadHandler.d.ts +7 -0
  39. package/dist/handlers/mediaUploadHandler.js +99 -0
  40. package/dist/handlers/mentionSuggestionHandler.js +16 -14
  41. package/dist/handlers/proposalDiffHandler.js +117 -50
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.js +39 -7
  44. package/dist/payload/collectionPermissions.js +3 -1
  45. package/dist/payload/logging.d.ts +9 -0
  46. package/dist/payload/logging.js +14 -0
  47. package/dist/payload/normalizeData.d.ts +16 -12
  48. package/dist/payload/normalizeData.js +47 -14
  49. package/dist/payload/proposalData.d.ts +31 -0
  50. package/dist/payload/proposalData.js +578 -0
  51. package/dist/payload/schemaContext.d.ts +3 -7
  52. package/dist/payload/schemaContext.js +147 -66
  53. package/package.json +6 -6
  54. package/dist/components/AIInput.js +0 -747
  55. /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
- for (const collection of collections){
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
- for (const locale of localesToSearch){
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 { getCollectionFields, getLocalizedRequiredFallbackData, normalizeAuthData, normalizeDataForFields } from "../payload/normalizeData.js";
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 = globalConfig.fields || [];
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
- for (const [locale, localeData] of Object.entries(proposal.localizedData)){
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 = applyLocalizedRequiredFallback({
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 normalized = normalizeDataForFields(globalConfig.fields || [], proposal.data);
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, normalized.data)),
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 = getCollectionFields(collectionConfig);
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
- for (const [locale, localeData] of Object.entries(proposal.localizedData)){
138
- const normalized = normalizeCollectionData(localeData);
139
- const fallbackSource = proposal.action === "create" ? createFallbackSource || {} : locale === defaultLocale ? await req.payload.findByID({
140
- collection: proposal.collection,
141
- depth: 2,
142
- fallbackLocale: false,
143
- id: proposal.id,
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
- const doc = await req.payload.findByID({
160
- collection: proposal.collection,
161
- depth: 2,
162
- fallbackLocale: false,
163
- id: proposal.id,
164
- locale,
165
- overrideAccess: false,
166
- req
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 normalized = normalizeCollectionData(proposal.data);
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(normalized.data),
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, normalized.data)),
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 auditLogCollectionSlug = "payload-ai-auditlog";
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: auditLogCollectionSlug,
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 === auditLogCollectionSlug)) {
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.filter((collection)=>!isInternalCollection(collection.slug)).filter((collection)=>collectionPermissions ? Boolean(collectionPermissions[collection.slug]?.read) : true).map((collection)=>collection.slug);
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#AIInput`);
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: auditLogCollectionSlug,
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: auditLogCollectionSlug
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.map((collection)=>collection.slug).filter((slug)=>!isInternalCollection(slug));
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
- blocks?: BlockConfig[];
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: FieldConfig[];
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 getCollectionFields: (collectionConfig?: CollectionConfig | null) => FieldConfig[];
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 {};