@mvriu5/payload-ai 0.5.9 → 0.5.10
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 +62 -6
- package/dist/ai/{proposals.d.ts → proposalSigning.d.ts} +1 -1
- package/dist/ai/{proposals.js → proposalSigning.js} +6 -11
- package/dist/ai/providerOptions.d.ts +17 -1
- package/dist/ai/providerOptions.js +35 -7
- package/dist/ai/providerRuntime.d.ts +2 -2
- package/dist/ai/providerRuntime.js +50 -16
- package/dist/ai/sensitiveData.js +2 -6
- package/dist/components/AIInput.d.ts +2 -1
- package/dist/components/AIInput.js +210 -39
- package/dist/components/AIInput.module.css +15 -1
- package/dist/components/APIKeyField.d.ts +3 -0
- package/dist/components/{AIApiKeyField.js → APIKeyField.js} +3 -4
- package/dist/components/ActionToast.d.ts +30 -0
- package/dist/components/{AIActionProposalList.js → ActionToast.js} +10 -8
- package/dist/components/AuditLogList.d.ts +32 -0
- package/dist/components/{RecentChangesList.js → AuditLogList.js} +13 -12
- package/dist/components/{RecentChangesList.module.css → AuditLogList.module.css} +18 -1
- package/dist/components/DiffDialog.d.ts +10 -3
- package/dist/components/DiffDialog.js +164 -78
- package/dist/components/DiffDialog.module.css +86 -2
- package/dist/components/Icons.d.ts +5 -0
- package/dist/components/Icons.js +109 -3
- package/dist/components/MentionPopover.d.ts +19 -0
- package/dist/components/{CollectionMentionPopover.js → MentionPopover.js} +11 -4
- package/dist/components/{CollectionMentionPopover.module.css → MentionPopover.module.css} +0 -2
- package/dist/components/hooks/useAISettings.d.ts +6 -4
- package/dist/components/hooks/useAISettings.js +18 -3
- package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +3 -3
- package/dist/components/hooks/useDocumentMentionSuggestions.js +2 -2
- package/dist/exports/client.d.ts +4 -2
- package/dist/exports/client.js +4 -2
- package/dist/handlers/applyActionHandler.d.ts +8 -0
- package/dist/handlers/applyActionHandler.js +563 -0
- package/dist/handlers/auditLogHandler.d.ts +6 -0
- package/dist/{endpoints/aiRecentChangesEndpointHandler.js → handlers/auditLogHandler.js} +12 -8
- package/dist/handlers/chatHandler.d.ts +42 -0
- package/dist/handlers/chatHandler.js +634 -0
- package/dist/handlers/mentionSuggestionHandler.d.ts +7 -0
- package/dist/handlers/mentionSuggestionHandler.js +97 -0
- package/dist/handlers/proposalDiffHandler.d.ts +7 -0
- package/dist/handlers/proposalDiffHandler.js +208 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +50 -18
- package/dist/payload/collectionPermissions.d.ts +14 -13
- package/dist/payload/collectionPermissions.js +3 -2
- package/dist/payload/normalizeData.d.ts +8 -0
- package/dist/payload/normalizeData.js +25 -3
- package/dist/payload/schemaContext.d.ts +43 -16
- package/dist/payload/schemaContext.js +139 -18
- package/dist/payload/shared.d.ts +33 -0
- package/dist/payload/shared.js +114 -0
- package/package.json +7 -2
- package/dist/components/AIActionProposalList.d.ts +0 -26
- package/dist/components/AIApiKeyField.d.ts +0 -2
- package/dist/components/CollectionMentionPopover.d.ts +0 -16
- package/dist/components/RecentChangesList.d.ts +0 -18
- package/dist/components/hooks/utils.d.ts +0 -1
- package/dist/components/hooks/utils.js +0 -3
- package/dist/endpoints/aiApplyActionEndpointHandler.d.ts +0 -8
- package/dist/endpoints/aiApplyActionEndpointHandler.js +0 -365
- package/dist/endpoints/aiChatEndpointHandler.d.ts +0 -35
- package/dist/endpoints/aiChatEndpointHandler.js +0 -382
- package/dist/endpoints/aiMentionSuggestionsEndpointHandler.d.ts +0 -7
- package/dist/endpoints/aiMentionSuggestionsEndpointHandler.js +0 -63
- package/dist/endpoints/aiProposalDiffEndpointHandler.d.ts +0 -7
- package/dist/endpoints/aiProposalDiffEndpointHandler.js +0 -138
- package/dist/endpoints/aiRecentChangesEndpointHandler.d.ts +0 -6
- /package/dist/components/{AIActionProposalList.module.css → ActionToast.module.css} +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
|
|
2
|
+
import { getDocLabel } from "../payload/shared.js";
|
|
3
|
+
const getLocaleCodes = (req)=>{
|
|
4
|
+
const localization = req.payload.config.localization;
|
|
5
|
+
if (!localization) return [];
|
|
6
|
+
if (Array.isArray(localization.localeCodes)) return localization.localeCodes;
|
|
7
|
+
const locales = localization.locales || [];
|
|
8
|
+
return locales.flatMap((locale)=>{
|
|
9
|
+
if (typeof locale === "string") return [
|
|
10
|
+
locale
|
|
11
|
+
];
|
|
12
|
+
if (locale && typeof locale === "object" && "code" in locale && typeof locale.code === "string") return [
|
|
13
|
+
locale.code
|
|
14
|
+
];
|
|
15
|
+
return [];
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
const hasLocalizedFields = (fields)=>{
|
|
19
|
+
return fields.some((field)=>{
|
|
20
|
+
return Boolean(field.localized) || Boolean(field.fields?.length && hasLocalizedFields(field.fields)) || Boolean(field.tabs?.some((tab)=>hasLocalizedFields(tab.fields || [])));
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
export const createMentionSuggestionHandler = (options = {})=>async (req)=>{
|
|
24
|
+
if (!req.user) return Response.json({
|
|
25
|
+
error: "Unauthorized"
|
|
26
|
+
}, {
|
|
27
|
+
status: 401
|
|
28
|
+
});
|
|
29
|
+
const body = req.json ? await req.json().catch(()=>null) : null;
|
|
30
|
+
const query = body?.query?.trim();
|
|
31
|
+
const collectionSlug = body?.collectionSlug?.trim();
|
|
32
|
+
if (!query && !collectionSlug) return Response.json({
|
|
33
|
+
suggestions: []
|
|
34
|
+
});
|
|
35
|
+
const suggestions = [];
|
|
36
|
+
const suggestionKeys = new Set();
|
|
37
|
+
const normalizedQuery = query?.toLowerCase();
|
|
38
|
+
const collections = req.payload.config.collections.filter((collection)=>{
|
|
39
|
+
if (!isCollectionActionAllowed({
|
|
40
|
+
action: "read",
|
|
41
|
+
permissions: options.collections,
|
|
42
|
+
req,
|
|
43
|
+
slug: collection.slug
|
|
44
|
+
})) return false;
|
|
45
|
+
if (collectionSlug) return collection.slug === collectionSlug;
|
|
46
|
+
return true;
|
|
47
|
+
});
|
|
48
|
+
const addSuggestion = ({ collectionSlug, doc, requireLabelMatch = false, useAsTitle })=>{
|
|
49
|
+
const id = doc.id?.toString();
|
|
50
|
+
if (!id) return;
|
|
51
|
+
const key = `${collectionSlug}:${id}`;
|
|
52
|
+
if (suggestionKeys.has(key)) return;
|
|
53
|
+
const label = getDocLabel(doc, useAsTitle);
|
|
54
|
+
if (requireLabelMatch && normalizedQuery && !label.toLowerCase().includes(normalizedQuery)) return;
|
|
55
|
+
suggestionKeys.add(key);
|
|
56
|
+
suggestions.push({
|
|
57
|
+
collection: collectionSlug,
|
|
58
|
+
id,
|
|
59
|
+
label,
|
|
60
|
+
slug: key,
|
|
61
|
+
type: "doc"
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
for (const collection of collections){
|
|
65
|
+
if (suggestions.length >= 5) break;
|
|
66
|
+
const collectionFields = collection.fields;
|
|
67
|
+
const localeCodes = query && hasLocalizedFields(collectionFields) ? getLocaleCodes(req) : [];
|
|
68
|
+
const localesToSearch = localeCodes.length > 0 ? localeCodes : [
|
|
69
|
+
null
|
|
70
|
+
];
|
|
71
|
+
for (const locale of localesToSearch){
|
|
72
|
+
if (suggestions.length >= 5) break;
|
|
73
|
+
const result = await req.payload.find({
|
|
74
|
+
collection: collection.slug,
|
|
75
|
+
depth: 0,
|
|
76
|
+
limit: query ? 100 : 10,
|
|
77
|
+
...locale ? {
|
|
78
|
+
locale
|
|
79
|
+
} : {},
|
|
80
|
+
overrideAccess: false,
|
|
81
|
+
req
|
|
82
|
+
});
|
|
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
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return Response.json({
|
|
95
|
+
suggestions: suggestions.slice(0, 5)
|
|
96
|
+
});
|
|
97
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PayloadHandler } from "payload";
|
|
2
|
+
import { type ResolvedCollectionPermissionMap } from "../payload/collectionPermissions.js";
|
|
3
|
+
type ProposalDiffOptions = {
|
|
4
|
+
collections?: ResolvedCollectionPermissionMap;
|
|
5
|
+
};
|
|
6
|
+
export declare const createProposalDiffHandler: (options?: ProposalDiffOptions) => PayloadHandler;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { verifyActionProposal } from "../ai/proposalSigning.js";
|
|
2
|
+
import { redactSensitiveData } from "../ai/sensitiveData.js";
|
|
3
|
+
import { getCollectionFields, getLocalizedRequiredFallbackData, normalizeAuthData, normalizeDataForFields } from "../payload/normalizeData.js";
|
|
4
|
+
import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
|
|
5
|
+
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
|
+
export const createProposalDiffHandler = (options = {})=>async (req)=>{
|
|
14
|
+
if (!req.user) return Response.json({
|
|
15
|
+
error: "Unauthorized"
|
|
16
|
+
}, {
|
|
17
|
+
status: 401
|
|
18
|
+
});
|
|
19
|
+
const body = req.json ? await req.json().catch(()=>null) : null;
|
|
20
|
+
const proposal = body?.proposal;
|
|
21
|
+
if (!proposal) return Response.json({
|
|
22
|
+
error: "Proposal is required"
|
|
23
|
+
}, {
|
|
24
|
+
status: 400
|
|
25
|
+
});
|
|
26
|
+
if (!verifyActionProposal(proposal)) return Response.json({
|
|
27
|
+
error: "Proposal signature is invalid or expired."
|
|
28
|
+
}, {
|
|
29
|
+
status: 400
|
|
30
|
+
});
|
|
31
|
+
if (!isActionProposal(proposal)) return Response.json({
|
|
32
|
+
error: "Proposal is invalid."
|
|
33
|
+
}, {
|
|
34
|
+
status: 400
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
const defaultLocale = getDefaultLocale(req);
|
|
38
|
+
if (proposal.action === "updateGlobal") {
|
|
39
|
+
const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
|
|
40
|
+
if (!globalConfig) return Response.json({
|
|
41
|
+
error: "Unknown global"
|
|
42
|
+
}, {
|
|
43
|
+
status: 400
|
|
44
|
+
});
|
|
45
|
+
if (hasLocalizedData(proposal)) {
|
|
46
|
+
const beforeByLocale = {};
|
|
47
|
+
const afterByLocale = {};
|
|
48
|
+
const globalFields = globalConfig.fields || [];
|
|
49
|
+
const defaultLocaleDoc = defaultLocale ? await req.payload.findGlobal({
|
|
50
|
+
depth: 2,
|
|
51
|
+
fallbackLocale: false,
|
|
52
|
+
locale: defaultLocale,
|
|
53
|
+
overrideAccess: false,
|
|
54
|
+
req,
|
|
55
|
+
slug: proposal.slug
|
|
56
|
+
}) : null;
|
|
57
|
+
for (const [locale, localeData] of Object.entries(proposal.localizedData)){
|
|
58
|
+
const normalized = normalizeDataForFields(globalFields, localeData);
|
|
59
|
+
const doc = await req.payload.findGlobal({
|
|
60
|
+
depth: 2,
|
|
61
|
+
fallbackLocale: false,
|
|
62
|
+
locale,
|
|
63
|
+
overrideAccess: false,
|
|
64
|
+
req,
|
|
65
|
+
slug: proposal.slug
|
|
66
|
+
});
|
|
67
|
+
const completedData = applyLocalizedRequiredFallback({
|
|
68
|
+
data: normalized.data,
|
|
69
|
+
fallbackSource: locale === defaultLocale ? doc : defaultLocaleDoc || doc,
|
|
70
|
+
fields: globalFields
|
|
71
|
+
});
|
|
72
|
+
beforeByLocale[locale] = redactSensitiveData(doc);
|
|
73
|
+
afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
|
|
74
|
+
}
|
|
75
|
+
return Response.json({
|
|
76
|
+
after: afterByLocale,
|
|
77
|
+
before: beforeByLocale
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const normalized = normalizeDataForFields(globalConfig.fields || [], proposal.data);
|
|
81
|
+
const doc = await req.payload.findGlobal({
|
|
82
|
+
depth: 2,
|
|
83
|
+
...proposal.locale ? {
|
|
84
|
+
locale: proposal.locale
|
|
85
|
+
} : {},
|
|
86
|
+
overrideAccess: false,
|
|
87
|
+
req,
|
|
88
|
+
slug: proposal.slug
|
|
89
|
+
});
|
|
90
|
+
return Response.json({
|
|
91
|
+
after: redactSensitiveData(mergeData(doc, normalized.data)),
|
|
92
|
+
before: redactSensitiveData(doc)
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (!isCollectionActionAllowed({
|
|
96
|
+
action: proposal.action === "delete" ? "delete" : "read",
|
|
97
|
+
permissions: options.collections,
|
|
98
|
+
req,
|
|
99
|
+
slug: proposal.collection
|
|
100
|
+
})) return Response.json({
|
|
101
|
+
error: "Unknown collection"
|
|
102
|
+
}, {
|
|
103
|
+
status: 400
|
|
104
|
+
});
|
|
105
|
+
if (proposal.action === "delete") {
|
|
106
|
+
const doc = await req.payload.findByID({
|
|
107
|
+
collection: proposal.collection,
|
|
108
|
+
depth: 2,
|
|
109
|
+
id: proposal.id,
|
|
110
|
+
...proposal.locale ? {
|
|
111
|
+
locale: proposal.locale
|
|
112
|
+
} : {},
|
|
113
|
+
overrideAccess: false,
|
|
114
|
+
req
|
|
115
|
+
});
|
|
116
|
+
return Response.json({
|
|
117
|
+
after: {},
|
|
118
|
+
before: redactSensitiveData(doc)
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
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));
|
|
124
|
+
if (hasLocalizedData(proposal)) {
|
|
125
|
+
const afterByLocale = {};
|
|
126
|
+
const beforeByLocale = {};
|
|
127
|
+
const defaultLocaleDoc = proposal.action === "update" && defaultLocale ? await req.payload.findByID({
|
|
128
|
+
collection: proposal.collection,
|
|
129
|
+
depth: 2,
|
|
130
|
+
fallbackLocale: false,
|
|
131
|
+
id: proposal.id,
|
|
132
|
+
locale: defaultLocale,
|
|
133
|
+
overrideAccess: false,
|
|
134
|
+
req
|
|
135
|
+
}) : null;
|
|
136
|
+
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") {
|
|
154
|
+
beforeByLocale[locale] = {};
|
|
155
|
+
afterByLocale[locale] = redactSensitiveData(completedData);
|
|
156
|
+
createFallbackSource = mergeData(createFallbackSource || {}, completedData);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
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
|
|
167
|
+
});
|
|
168
|
+
beforeByLocale[locale] = redactSensitiveData(doc);
|
|
169
|
+
afterByLocale[locale] = redactSensitiveData(mergeData(doc, completedData));
|
|
170
|
+
}
|
|
171
|
+
return Response.json({
|
|
172
|
+
after: afterByLocale,
|
|
173
|
+
before: beforeByLocale
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const normalized = normalizeCollectionData(proposal.data);
|
|
177
|
+
if (proposal.action === "create") {
|
|
178
|
+
return Response.json({
|
|
179
|
+
after: redactSensitiveData(normalized.data),
|
|
180
|
+
before: {}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const doc = await req.payload.findByID({
|
|
184
|
+
collection: proposal.collection,
|
|
185
|
+
depth: 2,
|
|
186
|
+
id: proposal.id,
|
|
187
|
+
...proposal.locale ? {
|
|
188
|
+
locale: proposal.locale
|
|
189
|
+
} : {},
|
|
190
|
+
overrideAccess: false,
|
|
191
|
+
req
|
|
192
|
+
});
|
|
193
|
+
return Response.json({
|
|
194
|
+
after: redactSensitiveData(mergeData(doc, normalized.data)),
|
|
195
|
+
before: redactSensitiveData(doc)
|
|
196
|
+
});
|
|
197
|
+
} catch (err) {
|
|
198
|
+
req.payload.logger.error({
|
|
199
|
+
err,
|
|
200
|
+
msg: "AI proposal diff failed"
|
|
201
|
+
});
|
|
202
|
+
return Response.json({
|
|
203
|
+
error: "Could not load proposal diff."
|
|
204
|
+
}, {
|
|
205
|
+
status: 400
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Config } from "payload";
|
|
2
2
|
import { type AIModelConfig } from "./ai/providerOptions.js";
|
|
3
|
-
import { type
|
|
4
|
-
export type
|
|
3
|
+
import { type CollectionPermissionMap } from "./payload/collectionPermissions.js";
|
|
4
|
+
export type PayloadAIPluginOptions = {
|
|
5
5
|
allowUserApiKeys?: boolean;
|
|
6
|
-
collections?:
|
|
6
|
+
collections?: CollectionPermissionMap;
|
|
7
7
|
disabled?: boolean;
|
|
8
|
+
maxOutputTokens?: number;
|
|
8
9
|
models?: AIModelConfig;
|
|
9
10
|
};
|
|
10
|
-
export
|
|
11
|
-
export declare const payloadAiPlugin: (pluginOptions: PayloadAiPluginOptions) => (config: Config) => Config;
|
|
11
|
+
export declare const payloadAiPlugin: (pluginOptions: PayloadAIPluginOptions) => (config: Config) => Config;
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { aiProviders, getResolvedAIModelConfig } from "./ai/providerOptions.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
2
|
+
import { createApplyActionHandler } from "./handlers/applyActionHandler.js";
|
|
3
|
+
import { createChatHandler } from "./handlers/chatHandler.js";
|
|
4
|
+
import { createMentionSuggestionHandler } from "./handlers/mentionSuggestionHandler.js";
|
|
5
|
+
import { createProposalDiffHandler } from "./handlers/proposalDiffHandler.js";
|
|
6
|
+
import { createAuditLogHandler } from "./handlers/auditLogHandler.js";
|
|
7
7
|
import { resolveCollectionPermissions } from "./payload/collectionPermissions.js";
|
|
8
8
|
import { isInternalCollection } from "./payload/shared.js";
|
|
9
|
-
const
|
|
9
|
+
const auditLogCollectionSlug = "payload-ai-auditlog";
|
|
10
10
|
const createAIChangesCollection = ()=>({
|
|
11
|
-
slug:
|
|
11
|
+
slug: auditLogCollectionSlug,
|
|
12
12
|
access: {
|
|
13
13
|
create: ()=>false,
|
|
14
14
|
delete: ()=>false,
|
|
@@ -26,6 +26,10 @@ const createAIChangesCollection = ()=>({
|
|
|
26
26
|
group: "AI",
|
|
27
27
|
useAsTitle: "title"
|
|
28
28
|
},
|
|
29
|
+
labels: {
|
|
30
|
+
plural: "AI Changes",
|
|
31
|
+
singular: "AI Change"
|
|
32
|
+
},
|
|
29
33
|
fields: [
|
|
30
34
|
{
|
|
31
35
|
name: "title",
|
|
@@ -90,9 +94,33 @@ const createAIChangesCollection = ()=>({
|
|
|
90
94
|
name: "proposal",
|
|
91
95
|
type: "json"
|
|
92
96
|
},
|
|
97
|
+
{
|
|
98
|
+
name: "prompt",
|
|
99
|
+
type: "textarea"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "inputTokens",
|
|
103
|
+
type: "number"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "outputTokens",
|
|
107
|
+
type: "number"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "totalTokens",
|
|
111
|
+
type: "number"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "aiResponse",
|
|
115
|
+
type: "textarea"
|
|
116
|
+
},
|
|
93
117
|
{
|
|
94
118
|
name: "userID",
|
|
95
119
|
type: "text"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "userLabel",
|
|
123
|
+
type: "text"
|
|
96
124
|
}
|
|
97
125
|
],
|
|
98
126
|
timestamps: true
|
|
@@ -106,6 +134,7 @@ const addAccountFields = ({ allowUserApiKeys, config })=>{
|
|
|
106
134
|
name: "aiProvider",
|
|
107
135
|
type: "select",
|
|
108
136
|
defaultValue: "openai",
|
|
137
|
+
label: "AI Provider",
|
|
109
138
|
options: aiProviders
|
|
110
139
|
});
|
|
111
140
|
if (allowUserApiKeys) {
|
|
@@ -117,7 +146,8 @@ const addAccountFields = ({ allowUserApiKeys, config })=>{
|
|
|
117
146
|
Field: "payload-ai-plugin/client#AIApiKeyField"
|
|
118
147
|
},
|
|
119
148
|
description: "Optional. If empty, the plugin uses the provider API key from environment variables."
|
|
120
|
-
}
|
|
149
|
+
},
|
|
150
|
+
label: "AI API Key"
|
|
121
151
|
});
|
|
122
152
|
}
|
|
123
153
|
};
|
|
@@ -126,8 +156,9 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
126
156
|
const collectionPermissions = resolveCollectionPermissions(pluginOptions.collections);
|
|
127
157
|
const allowUserApiKeys = pluginOptions.allowUserApiKeys !== false;
|
|
128
158
|
const modelConfig = getResolvedAIModelConfig(pluginOptions.models);
|
|
159
|
+
const maxOutputTokens = typeof pluginOptions.maxOutputTokens === "number" && Number.isFinite(pluginOptions.maxOutputTokens) && pluginOptions.maxOutputTokens > 0 ? Math.floor(pluginOptions.maxOutputTokens) : undefined;
|
|
129
160
|
if (!config.collections) config.collections = [];
|
|
130
|
-
if (!config.collections.some((collection)=>collection.slug ===
|
|
161
|
+
if (!config.collections.some((collection)=>collection.slug === auditLogCollectionSlug)) {
|
|
131
162
|
config.collections.push(createAIChangesCollection());
|
|
132
163
|
}
|
|
133
164
|
addAccountFields({
|
|
@@ -151,42 +182,43 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
151
182
|
if (!config.admin.components.beforeDashboard) config.admin.components.beforeDashboard = [];
|
|
152
183
|
config.admin.components.beforeDashboard.push(`payload-ai-plugin/client#AIInput`);
|
|
153
184
|
config.endpoints.push({
|
|
154
|
-
handler:
|
|
185
|
+
handler: createChatHandler({
|
|
155
186
|
allowUserApiKeys,
|
|
156
187
|
collections: collectionPermissions,
|
|
188
|
+
maxOutputTokens,
|
|
157
189
|
models: modelConfig
|
|
158
190
|
}),
|
|
159
191
|
method: "post",
|
|
160
192
|
path: "/ai-chat"
|
|
161
193
|
});
|
|
162
194
|
config.endpoints.push({
|
|
163
|
-
handler:
|
|
164
|
-
changeLogCollection:
|
|
195
|
+
handler: createApplyActionHandler({
|
|
196
|
+
changeLogCollection: auditLogCollectionSlug,
|
|
165
197
|
collections: collectionPermissions
|
|
166
198
|
}),
|
|
167
199
|
method: "post",
|
|
168
200
|
path: "/ai-apply-action"
|
|
169
201
|
});
|
|
170
202
|
config.endpoints.push({
|
|
171
|
-
handler:
|
|
172
|
-
changeLogCollection:
|
|
203
|
+
handler: createAuditLogHandler({
|
|
204
|
+
changeLogCollection: auditLogCollectionSlug
|
|
173
205
|
}),
|
|
174
206
|
method: "get",
|
|
175
|
-
path: "/ai-
|
|
207
|
+
path: "/ai-audit-log"
|
|
176
208
|
});
|
|
177
209
|
config.endpoints.push({
|
|
178
|
-
handler:
|
|
210
|
+
handler: createProposalDiffHandler({
|
|
179
211
|
collections: collectionPermissions
|
|
180
212
|
}),
|
|
181
213
|
method: "post",
|
|
182
214
|
path: "/ai-proposal-diff"
|
|
183
215
|
});
|
|
184
216
|
config.endpoints.push({
|
|
185
|
-
handler:
|
|
217
|
+
handler: createMentionSuggestionHandler({
|
|
186
218
|
collections: collectionPermissions
|
|
187
219
|
}),
|
|
188
220
|
method: "post",
|
|
189
|
-
path: "/ai-mention-
|
|
221
|
+
path: "/ai-mention-suggestion"
|
|
190
222
|
});
|
|
191
223
|
if (incomingOnInit) {
|
|
192
224
|
config.onInit = async (payload)=>{
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import type { PayloadHandler } from "payload";
|
|
2
|
-
export type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type
|
|
6
|
-
export type
|
|
7
|
-
export declare const resolveCollectionPermissions: (collections?:
|
|
2
|
+
export type CollectionAction = "create" | "delete" | "read" | "update";
|
|
3
|
+
type CollectionPermissions = Partial<Record<CollectionAction, boolean>>;
|
|
4
|
+
type CollectionPermissionConfig = true | CollectionPermissions;
|
|
5
|
+
export type CollectionPermissionMap = Partial<Record<string, CollectionPermissionConfig>>;
|
|
6
|
+
export type ResolvedCollectionPermissionMap = Partial<Record<string, Record<CollectionAction, boolean>>>;
|
|
7
|
+
export declare const resolveCollectionPermissions: (collections?: CollectionPermissionMap) => ResolvedCollectionPermissionMap | undefined;
|
|
8
8
|
export declare const getCollectionSlugsForAction: ({ action, permissions, req, }: {
|
|
9
|
-
action:
|
|
10
|
-
permissions?:
|
|
9
|
+
action: CollectionAction;
|
|
10
|
+
permissions?: ResolvedCollectionPermissionMap;
|
|
11
11
|
req: Parameters<PayloadHandler>[0];
|
|
12
12
|
}) => string[];
|
|
13
13
|
export declare const isCollectionActionAllowed: ({ action, permissions, req, slug, }: {
|
|
14
|
-
action:
|
|
15
|
-
permissions?:
|
|
14
|
+
action: CollectionAction;
|
|
15
|
+
permissions?: ResolvedCollectionPermissionMap;
|
|
16
16
|
req: Parameters<PayloadHandler>[0];
|
|
17
17
|
slug: string;
|
|
18
18
|
}) => boolean;
|
|
19
|
-
export declare const getCollectionPermissions: ({ permissions, slug
|
|
20
|
-
permissions?:
|
|
19
|
+
export declare const getCollectionPermissions: ({ permissions, slug }: {
|
|
20
|
+
permissions?: ResolvedCollectionPermissionMap;
|
|
21
21
|
slug: string;
|
|
22
|
-
}) => Record<
|
|
22
|
+
}) => Record<CollectionAction, boolean>;
|
|
23
|
+
export {};
|
|
@@ -10,7 +10,7 @@ const getKnownCollectionSlugs = (req)=>{
|
|
|
10
10
|
};
|
|
11
11
|
export const resolveCollectionPermissions = (collections)=>{
|
|
12
12
|
if (!collections) return undefined;
|
|
13
|
-
|
|
13
|
+
const entries = Object.entries(collections).map(([slug, config])=>{
|
|
14
14
|
if (config === true) {
|
|
15
15
|
return [
|
|
16
16
|
slug,
|
|
@@ -42,7 +42,8 @@ export const resolveCollectionPermissions = (collections)=>{
|
|
|
42
42
|
update: Boolean(config.update)
|
|
43
43
|
}
|
|
44
44
|
];
|
|
45
|
-
})
|
|
45
|
+
});
|
|
46
|
+
return Object.fromEntries(entries);
|
|
46
47
|
};
|
|
47
48
|
export const getCollectionSlugsForAction = ({ action, permissions, req })=>{
|
|
48
49
|
const knownSlugs = getKnownCollectionSlugs(req);
|
|
@@ -2,10 +2,13 @@ export type FieldConfig = {
|
|
|
2
2
|
blocks?: BlockConfig[];
|
|
3
3
|
defaultValue?: unknown;
|
|
4
4
|
fields?: FieldConfig[];
|
|
5
|
+
label?: unknown;
|
|
6
|
+
localized?: boolean;
|
|
5
7
|
name?: string;
|
|
6
8
|
options?: (string | {
|
|
7
9
|
value?: string;
|
|
8
10
|
})[];
|
|
11
|
+
required?: boolean;
|
|
9
12
|
type?: string;
|
|
10
13
|
};
|
|
11
14
|
type BlockConfig = {
|
|
@@ -26,4 +29,9 @@ export declare const isAuthCollection: (collectionConfig?: CollectionConfig | nu
|
|
|
26
29
|
export declare const getCollectionFields: (collectionConfig?: CollectionConfig | null) => FieldConfig[];
|
|
27
30
|
export declare const normalizeAuthData: (collectionConfig: CollectionConfig | undefined, normalized: NormalizedData) => NormalizedData;
|
|
28
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>;
|
|
29
37
|
export {};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
+
import { isRecord } from "./shared.js";
|
|
1
2
|
const SKIP_FIELD = Symbol("skipField");
|
|
2
|
-
const isRecord = (value)=>{
|
|
3
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4
|
-
};
|
|
5
3
|
const getNamedFields = (fields)=>{
|
|
6
4
|
return fields.filter((field)=>Boolean(field.name));
|
|
7
5
|
};
|
|
@@ -190,3 +188,27 @@ export const normalizeDataForFields = (fields, data)=>{
|
|
|
190
188
|
droppedFields
|
|
191
189
|
};
|
|
192
190
|
};
|
|
191
|
+
export const getLocalizedRequiredFallbackData = ({ fields, source, target })=>{
|
|
192
|
+
const fallbackData = {};
|
|
193
|
+
for (const field of getNamedFields(fields)){
|
|
194
|
+
const targetValue = target[field.name];
|
|
195
|
+
const sourceValue = source[field.name];
|
|
196
|
+
if (field.localized && field.required) {
|
|
197
|
+
if (targetValue === undefined && sourceValue !== undefined) {
|
|
198
|
+
fallbackData[field.name] = sourceValue;
|
|
199
|
+
}
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (field.type === "group" && field.fields?.length && isRecord(sourceValue)) {
|
|
203
|
+
const nestedFallback = getLocalizedRequiredFallbackData({
|
|
204
|
+
fields: field.fields,
|
|
205
|
+
source: sourceValue,
|
|
206
|
+
target: isRecord(targetValue) ? targetValue : {}
|
|
207
|
+
});
|
|
208
|
+
if (Object.keys(nestedFallback).length > 0) {
|
|
209
|
+
fallbackData[field.name] = nestedFallback;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return fallbackData;
|
|
214
|
+
};
|