@mvriu5/payload-ai 0.5.8 → 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 +327 -41
- package/dist/components/AIInput.module.css +41 -6
- 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} +78 -8
- package/dist/components/{AIActionProposalList.module.css → ActionToast.module.css} +49 -5
- package/dist/components/AuditLogList.d.ts +32 -0
- package/dist/components/AuditLogList.js +67 -0
- package/dist/components/AuditLogList.module.css +152 -0
- package/dist/components/DiffDialog.d.ts +19 -0
- package/dist/components/DiffDialog.js +396 -0
- package/dist/components/DiffDialog.module.css +333 -0
- package/dist/components/Icons.d.ts +9 -0
- package/dist/components/Icons.js +193 -0
- 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/handlers/auditLogHandler.js +39 -0
- 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 +151 -8
- 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 +79 -9
- package/dist/payload/schemaContext.js +231 -22
- 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 -25
- package/dist/components/AIApiKeyField.d.ts +0 -2
- package/dist/components/CollectionMentionPopover.d.ts +0 -16
- package/dist/components/hooks/utils.d.ts +0 -1
- package/dist/components/hooks/utils.js +0 -3
- package/dist/endpoints/aiApplyActionEndpointHandler.d.ts +0 -7
- package/dist/endpoints/aiApplyActionEndpointHandler.js +0 -169
- package/dist/endpoints/aiChatEndpointHandler.d.ts +0 -35
- package/dist/endpoints/aiChatEndpointHandler.js +0 -321
- package/dist/endpoints/aiMentionSuggestionsEndpointHandler.d.ts +0 -7
- package/dist/endpoints/aiMentionSuggestionsEndpointHandler.js +0 -63
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import type { PayloadHandler } from "payload";
|
|
2
|
-
import { type
|
|
3
|
-
export type
|
|
2
|
+
import { type ResolvedCollectionPermissionMap } from "./collectionPermissions.js";
|
|
3
|
+
export type ChatMention = {
|
|
4
4
|
collection?: string;
|
|
5
5
|
id?: string;
|
|
6
6
|
label?: string;
|
|
7
7
|
parent?: string;
|
|
8
8
|
slug?: string;
|
|
9
|
-
type?: "block" | "collection" | "doc" | "global";
|
|
9
|
+
type?: "block" | "collection" | "doc" | "global" | "locale";
|
|
10
10
|
};
|
|
11
11
|
export type FieldConfig = {
|
|
12
|
+
admin?: {
|
|
13
|
+
condition?: unknown;
|
|
14
|
+
};
|
|
12
15
|
blocks?: BlockConfig[];
|
|
16
|
+
defaultValue?: unknown;
|
|
13
17
|
fields?: FieldConfig[];
|
|
14
18
|
hasMany?: boolean;
|
|
15
19
|
label?: unknown;
|
|
20
|
+
localized?: boolean;
|
|
16
21
|
name?: string;
|
|
22
|
+
options?: (string | {
|
|
23
|
+
label?: unknown;
|
|
24
|
+
value?: string;
|
|
25
|
+
})[];
|
|
17
26
|
relationTo?: unknown;
|
|
18
27
|
required?: boolean;
|
|
19
28
|
type?: string;
|
|
@@ -26,27 +35,88 @@ type BlockConfig = {
|
|
|
26
35
|
};
|
|
27
36
|
slug: string;
|
|
28
37
|
};
|
|
38
|
+
type VersionConfig = boolean | {
|
|
39
|
+
drafts?: boolean | Record<string, unknown>;
|
|
40
|
+
};
|
|
41
|
+
type CollectionLikeConfig = {
|
|
42
|
+
admin?: {
|
|
43
|
+
useAsTitle?: string;
|
|
44
|
+
};
|
|
45
|
+
access?: Record<string, unknown>;
|
|
46
|
+
auth?: unknown;
|
|
47
|
+
fields?: FieldConfig[];
|
|
48
|
+
label?: unknown;
|
|
49
|
+
labels?: {
|
|
50
|
+
plural?: unknown;
|
|
51
|
+
singular?: unknown;
|
|
52
|
+
};
|
|
53
|
+
slug: string;
|
|
54
|
+
versions?: VersionConfig;
|
|
55
|
+
};
|
|
29
56
|
type MentionContext = {
|
|
30
57
|
blockContexts: (Record<string, unknown> & {
|
|
31
58
|
parent: string;
|
|
32
59
|
slug: string;
|
|
33
60
|
})[];
|
|
34
61
|
collectionSlugs: string[];
|
|
62
|
+
collections?: ResolvedCollectionPermissionMap;
|
|
35
63
|
globalSlugs: string[];
|
|
36
|
-
mentions?:
|
|
64
|
+
mentions?: ChatMention[];
|
|
37
65
|
req: Parameters<PayloadHandler>[0];
|
|
38
66
|
};
|
|
39
|
-
export declare const
|
|
40
|
-
|
|
67
|
+
export declare const describeCollectionLikeSummary: ({ config, permissions, type, }: {
|
|
68
|
+
config: CollectionLikeConfig;
|
|
69
|
+
permissions?: ResolvedCollectionPermissionMap;
|
|
70
|
+
type: "collection" | "global";
|
|
71
|
+
}) => {
|
|
72
|
+
access: {
|
|
73
|
+
aiPermissions: Record<import("./collectionPermissions.js").CollectionAction, boolean> | {
|
|
74
|
+
read: boolean;
|
|
75
|
+
update: boolean;
|
|
76
|
+
};
|
|
77
|
+
hasPayloadAccessControl: boolean;
|
|
78
|
+
};
|
|
79
|
+
hasAuth: boolean | undefined;
|
|
80
|
+
hasDrafts: boolean | undefined;
|
|
81
|
+
hasLocalizedFields: boolean | undefined;
|
|
82
|
+
label: unknown;
|
|
83
|
+
localizedFieldNames: string[] | undefined;
|
|
84
|
+
slug: string;
|
|
85
|
+
type: "collection" | "global";
|
|
86
|
+
useAsTitle: string | undefined;
|
|
87
|
+
};
|
|
88
|
+
export declare const describeCollectionLikeConfig: ({ config, permissions, type, }: {
|
|
89
|
+
config: CollectionLikeConfig;
|
|
90
|
+
permissions?: ResolvedCollectionPermissionMap;
|
|
91
|
+
type: "collection" | "global";
|
|
92
|
+
}) => {
|
|
93
|
+
fields: Record<string, unknown>[];
|
|
94
|
+
access: {
|
|
95
|
+
aiPermissions: Record<import("./collectionPermissions.js").CollectionAction, boolean> | {
|
|
96
|
+
read: boolean;
|
|
97
|
+
update: boolean;
|
|
98
|
+
};
|
|
99
|
+
hasPayloadAccessControl: boolean;
|
|
100
|
+
};
|
|
101
|
+
hasAuth: boolean | undefined;
|
|
102
|
+
hasDrafts: boolean | undefined;
|
|
103
|
+
hasLocalizedFields: boolean | undefined;
|
|
104
|
+
label: unknown;
|
|
105
|
+
localizedFieldNames: string[] | undefined;
|
|
106
|
+
slug: string;
|
|
107
|
+
type: "collection" | "global";
|
|
108
|
+
useAsTitle: string | undefined;
|
|
109
|
+
};
|
|
110
|
+
export declare const collectBlocks: ({ fields, parent }: {
|
|
41
111
|
fields: FieldConfig[];
|
|
42
112
|
parent: string;
|
|
43
113
|
}) => (Record<string, unknown> & {
|
|
44
114
|
parent: string;
|
|
45
115
|
slug: string;
|
|
46
116
|
})[];
|
|
47
|
-
export declare const getAllowedCollectionSlugs: (req: Parameters<PayloadHandler>[0], collections?:
|
|
48
|
-
export declare const getMentionContext: ({ blockContexts, collectionSlugs, globalSlugs, mentions, req
|
|
49
|
-
export declare const buildPromptWithMentionContext: ({ mentionContext, prompt
|
|
117
|
+
export declare const getAllowedCollectionSlugs: (req: Parameters<PayloadHandler>[0], collections?: ResolvedCollectionPermissionMap) => string[];
|
|
118
|
+
export declare const getMentionContext: ({ blockContexts, collectionSlugs, collections, globalSlugs, mentions, req }: MentionContext) => Promise<Record<string, unknown>[]>;
|
|
119
|
+
export declare const buildPromptWithMentionContext: ({ mentionContext, prompt }: {
|
|
50
120
|
mentionContext: Record<string, unknown>[];
|
|
51
121
|
prompt: string;
|
|
52
122
|
}) => string;
|
|
@@ -1,18 +1,172 @@
|
|
|
1
|
-
import { getCollectionSlugsForAction } from "./collectionPermissions.js";
|
|
2
|
-
import {
|
|
1
|
+
import { getCollectionPermissions, getCollectionSlugsForAction } from "./collectionPermissions.js";
|
|
2
|
+
import { getCollectionFields, isAuthCollection } from "./normalizeData.js";
|
|
3
|
+
import { getSerializableLabel, isInternalCollection, isRecord } from "./shared.js";
|
|
3
4
|
const getSerializableRelationTo = (relationTo)=>{
|
|
4
|
-
if (typeof relationTo === "string")
|
|
5
|
-
return relationTo;
|
|
6
|
-
}
|
|
5
|
+
if (typeof relationTo === "string") return relationTo;
|
|
7
6
|
if (Array.isArray(relationTo) && relationTo.every((item)=>typeof item === "string")) {
|
|
8
7
|
return relationTo;
|
|
9
8
|
}
|
|
10
9
|
return undefined;
|
|
11
10
|
};
|
|
12
|
-
|
|
11
|
+
const fieldDescriptionCache = new WeakMap();
|
|
12
|
+
const blockDescriptionCache = new WeakMap();
|
|
13
|
+
const collectionDescriptionCache = new WeakMap();
|
|
14
|
+
const getLocaleConfigs = (req)=>{
|
|
15
|
+
const localization = req.payload.config.localization;
|
|
16
|
+
const defaultLocale = localization?.defaultLocale;
|
|
17
|
+
const locales = localization?.locales || [];
|
|
18
|
+
return locales.flatMap((locale)=>{
|
|
19
|
+
if (typeof locale === "string") {
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
code: locale,
|
|
23
|
+
...locale === defaultLocale ? {
|
|
24
|
+
isDefault: true
|
|
25
|
+
} : {},
|
|
26
|
+
label: locale
|
|
27
|
+
}
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
if (!isRecord(locale)) return [];
|
|
31
|
+
const code = "code" in locale && typeof locale.code === "string" ? locale.code : "label" in locale && typeof locale.label === "string" ? locale.label : undefined;
|
|
32
|
+
if (!code) return [];
|
|
33
|
+
const label = "label" in locale ? getSerializableLabel(locale.label, code) : code;
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
code,
|
|
37
|
+
...code === defaultLocale ? {
|
|
38
|
+
isDefault: true
|
|
39
|
+
} : {},
|
|
40
|
+
label
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
const getSerializableOptions = (options)=>{
|
|
46
|
+
if (!Array.isArray(options)) return undefined;
|
|
47
|
+
const serializedOptions = options.map((option)=>{
|
|
48
|
+
if (typeof option === "string") {
|
|
49
|
+
return {
|
|
50
|
+
label: option,
|
|
51
|
+
value: option
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (!isRecord(option)) return null;
|
|
55
|
+
const label = "label" in option ? getSerializableLabel(option.label) : undefined;
|
|
56
|
+
const value = "value" in option && typeof option.value === "string" ? option.value : undefined;
|
|
57
|
+
if (!label && !value) return null;
|
|
58
|
+
return {
|
|
59
|
+
...label ? {
|
|
60
|
+
label
|
|
61
|
+
} : {},
|
|
62
|
+
...value ? {
|
|
63
|
+
value
|
|
64
|
+
} : {}
|
|
65
|
+
};
|
|
66
|
+
}).filter(Boolean);
|
|
67
|
+
return serializedOptions.length > 0 ? serializedOptions : undefined;
|
|
68
|
+
};
|
|
69
|
+
const hasDrafts = (config)=>{
|
|
70
|
+
const versions = config?.versions;
|
|
71
|
+
if (!versions || versions === true) return false;
|
|
72
|
+
if (!isRecord(versions)) return false;
|
|
73
|
+
return Boolean(versions.drafts);
|
|
74
|
+
};
|
|
75
|
+
const toNormalizeCollectionConfig = (config)=>{
|
|
76
|
+
if (!config) return undefined;
|
|
77
|
+
return {
|
|
78
|
+
auth: config.auth,
|
|
79
|
+
fields: config.fields || [],
|
|
80
|
+
slug: config.slug
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
const getSchemaFields = (config)=>{
|
|
84
|
+
const normalizedConfig = toNormalizeCollectionConfig(config);
|
|
85
|
+
const fields = isAuthCollection(normalizedConfig) ? getCollectionFields(normalizedConfig) : [
|
|
86
|
+
...config?.fields || []
|
|
87
|
+
];
|
|
88
|
+
if (hasDrafts(config)) {
|
|
89
|
+
fields.push({
|
|
90
|
+
label: "Status",
|
|
91
|
+
name: "_status",
|
|
92
|
+
options: [
|
|
93
|
+
"draft",
|
|
94
|
+
"published"
|
|
95
|
+
],
|
|
96
|
+
required: true,
|
|
97
|
+
type: "select"
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return fields;
|
|
101
|
+
};
|
|
102
|
+
const getLocalizedFieldNames = (fields, path = "")=>{
|
|
103
|
+
return fields.flatMap((field)=>{
|
|
104
|
+
const fieldPath = field.name ? path ? `${path}.${field.name}` : field.name : path;
|
|
105
|
+
const nestedFields = field.fields ? getLocalizedFieldNames(field.fields, fieldPath) : [];
|
|
106
|
+
const nestedBlocks = field.blocks ? field.blocks.flatMap((block)=>getLocalizedFieldNames(block.fields || [], `${fieldPath}.${block.slug}`)) : [];
|
|
107
|
+
return [
|
|
108
|
+
...field.localized && fieldPath ? [
|
|
109
|
+
fieldPath
|
|
110
|
+
] : [],
|
|
111
|
+
...nestedFields,
|
|
112
|
+
...nestedBlocks
|
|
113
|
+
];
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
const getCachedCollectionDescription = (config, type)=>{
|
|
117
|
+
const cached = collectionDescriptionCache.get(config);
|
|
118
|
+
if (cached) return cached;
|
|
119
|
+
const slug = config.slug;
|
|
120
|
+
const fields = getSchemaFields(config);
|
|
121
|
+
const localizedFieldNames = getLocalizedFieldNames(fields);
|
|
122
|
+
const description = {
|
|
123
|
+
fields: fields.map(describeField),
|
|
124
|
+
hasAuth: isAuthCollection(toNormalizeCollectionConfig(config)) || undefined,
|
|
125
|
+
hasDrafts: hasDrafts(config) || undefined,
|
|
126
|
+
hasLocalizedFields: localizedFieldNames.length > 0 || undefined,
|
|
127
|
+
label: type === "collection" ? config.labels?.plural || config.labels?.singular || slug : getSerializableLabel(config.label) || slug,
|
|
128
|
+
...localizedFieldNames.length > 0 ? {
|
|
129
|
+
localizedFieldNames
|
|
130
|
+
} : {},
|
|
131
|
+
...type === "collection" && config.admin?.useAsTitle ? {
|
|
132
|
+
useAsTitle: config.admin.useAsTitle
|
|
133
|
+
} : {},
|
|
134
|
+
slug
|
|
135
|
+
};
|
|
136
|
+
collectionDescriptionCache.set(config, description);
|
|
137
|
+
return description;
|
|
138
|
+
};
|
|
139
|
+
export const describeCollectionLikeSummary = ({ config, permissions, type })=>{
|
|
140
|
+
const description = getCachedCollectionDescription(config, type);
|
|
141
|
+
const slug = description.slug;
|
|
142
|
+
return {
|
|
143
|
+
access: {
|
|
144
|
+
aiPermissions: type === "collection" ? getCollectionPermissions({
|
|
145
|
+
permissions,
|
|
146
|
+
slug
|
|
147
|
+
}) : {
|
|
148
|
+
read: true,
|
|
149
|
+
update: true
|
|
150
|
+
},
|
|
151
|
+
hasPayloadAccessControl: Boolean(config.access)
|
|
152
|
+
},
|
|
153
|
+
hasAuth: description.hasAuth,
|
|
154
|
+
hasDrafts: description.hasDrafts,
|
|
155
|
+
hasLocalizedFields: description.hasLocalizedFields,
|
|
156
|
+
label: description.label,
|
|
157
|
+
localizedFieldNames: description.localizedFieldNames,
|
|
158
|
+
slug,
|
|
159
|
+
type,
|
|
160
|
+
useAsTitle: description.useAsTitle
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
const describeField = (field)=>{
|
|
164
|
+
const cached = fieldDescriptionCache.get(field);
|
|
165
|
+
if (cached) return cached;
|
|
13
166
|
const label = getSerializableLabel(field.label);
|
|
14
167
|
const relationTo = getSerializableRelationTo(field.relationTo);
|
|
15
|
-
|
|
168
|
+
const options = getSerializableOptions(field.options);
|
|
169
|
+
const description = {
|
|
16
170
|
...label ? {
|
|
17
171
|
label
|
|
18
172
|
} : {},
|
|
@@ -28,9 +182,18 @@ export const describeField = (field)=>{
|
|
|
28
182
|
...field.hasMany ? {
|
|
29
183
|
hasMany: field.hasMany
|
|
30
184
|
} : {},
|
|
185
|
+
...field.localized ? {
|
|
186
|
+
localized: field.localized
|
|
187
|
+
} : {},
|
|
31
188
|
...relationTo ? {
|
|
32
189
|
relationTo
|
|
33
190
|
} : {},
|
|
191
|
+
...options ? {
|
|
192
|
+
options
|
|
193
|
+
} : {},
|
|
194
|
+
...field.admin?.condition ? {
|
|
195
|
+
hasCondition: true
|
|
196
|
+
} : {},
|
|
34
197
|
...field.fields ? {
|
|
35
198
|
fields: field.fields.map(describeField)
|
|
36
199
|
} : {},
|
|
@@ -38,13 +201,29 @@ export const describeField = (field)=>{
|
|
|
38
201
|
blocks: field.blocks.map(describeBlock)
|
|
39
202
|
} : {}
|
|
40
203
|
};
|
|
204
|
+
fieldDescriptionCache.set(field, description);
|
|
205
|
+
return description;
|
|
41
206
|
};
|
|
42
207
|
const describeBlock = (block)=>{
|
|
43
|
-
|
|
208
|
+
const cached = blockDescriptionCache.get(block);
|
|
209
|
+
if (cached) return cached;
|
|
210
|
+
const description = {
|
|
44
211
|
fields: (block.fields || []).map(describeField),
|
|
45
212
|
label: getSerializableLabel(block.labels?.singular) || block.slug,
|
|
46
213
|
slug: block.slug
|
|
47
214
|
};
|
|
215
|
+
blockDescriptionCache.set(block, description);
|
|
216
|
+
return description;
|
|
217
|
+
};
|
|
218
|
+
export const describeCollectionLikeConfig = ({ config, permissions, type })=>{
|
|
219
|
+
return {
|
|
220
|
+
...describeCollectionLikeSummary({
|
|
221
|
+
config,
|
|
222
|
+
permissions,
|
|
223
|
+
type
|
|
224
|
+
}),
|
|
225
|
+
fields: getCachedCollectionDescription(config, type).fields
|
|
226
|
+
};
|
|
48
227
|
};
|
|
49
228
|
export const collectBlocks = ({ fields, parent })=>{
|
|
50
229
|
const blocks = [];
|
|
@@ -78,11 +257,34 @@ export const getAllowedCollectionSlugs = (req, collections)=>{
|
|
|
78
257
|
req
|
|
79
258
|
});
|
|
80
259
|
};
|
|
81
|
-
export const getMentionContext = async ({ blockContexts, collectionSlugs, globalSlugs, mentions, req })=>{
|
|
260
|
+
export const getMentionContext = async ({ blockContexts, collectionSlugs, collections, globalSlugs, mentions, req })=>{
|
|
82
261
|
if (!mentions || mentions.length === 0) return [];
|
|
83
262
|
const context = [];
|
|
84
263
|
const seen = new Set();
|
|
264
|
+
const localeConfigs = getLocaleConfigs(req);
|
|
265
|
+
const selectedLocales = (mentions.filter((mention)=>mention.type === "locale" && mention.slug).map((mention)=>mention.slug) || []).filter((locale, index, array)=>array.indexOf(locale) === index);
|
|
266
|
+
const activeLocale = selectedLocales.at(-1);
|
|
267
|
+
if (localeConfigs.length > 0) {
|
|
268
|
+
context.push({
|
|
269
|
+
activeLocale,
|
|
270
|
+
defaultLocale: localeConfigs.find((locale)=>locale.isDefault)?.code,
|
|
271
|
+
locales: localeConfigs,
|
|
272
|
+
selectedLocales,
|
|
273
|
+
type: "locales"
|
|
274
|
+
});
|
|
275
|
+
}
|
|
85
276
|
for (const mention of mentions.slice(0, 8)){
|
|
277
|
+
if (mention.type === "locale" && mention.slug) {
|
|
278
|
+
const locale = localeConfigs.find((item)=>item.code === mention.slug);
|
|
279
|
+
if (!locale) continue;
|
|
280
|
+
const key = `locale:${locale.code}`;
|
|
281
|
+
if (seen.has(key)) continue;
|
|
282
|
+
seen.add(key);
|
|
283
|
+
context.push({
|
|
284
|
+
...locale,
|
|
285
|
+
type: "locale"
|
|
286
|
+
});
|
|
287
|
+
}
|
|
86
288
|
if (mention.type === "collection" && mention.slug) {
|
|
87
289
|
const slug = mention.slug;
|
|
88
290
|
const key = `collection:${slug}`;
|
|
@@ -90,12 +292,11 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, global
|
|
|
90
292
|
const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === slug);
|
|
91
293
|
if (!collectionConfig) continue;
|
|
92
294
|
seen.add(key);
|
|
93
|
-
context.push({
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
slug,
|
|
295
|
+
context.push(describeCollectionLikeConfig({
|
|
296
|
+
config: collectionConfig,
|
|
297
|
+
permissions: collections,
|
|
97
298
|
type: "collection"
|
|
98
|
-
});
|
|
299
|
+
}));
|
|
99
300
|
}
|
|
100
301
|
if (mention.type === "global" && mention.slug) {
|
|
101
302
|
const slug = mention.slug;
|
|
@@ -105,17 +306,20 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, global
|
|
|
105
306
|
if (!globalConfig) continue;
|
|
106
307
|
const globalDoc = await req.payload.findGlobal({
|
|
107
308
|
depth: 2,
|
|
309
|
+
...activeLocale ? {
|
|
310
|
+
locale: activeLocale
|
|
311
|
+
} : {},
|
|
108
312
|
overrideAccess: false,
|
|
109
313
|
req,
|
|
110
314
|
slug: slug
|
|
111
315
|
}).catch(()=>null);
|
|
112
316
|
seen.add(key);
|
|
113
317
|
context.push({
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
318
|
+
...describeCollectionLikeConfig({
|
|
319
|
+
config: globalConfig,
|
|
320
|
+
type: "global"
|
|
321
|
+
}),
|
|
322
|
+
doc: globalDoc
|
|
119
323
|
});
|
|
120
324
|
}
|
|
121
325
|
if (mention.type === "block" && mention.slug) {
|
|
@@ -138,6 +342,9 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, global
|
|
|
138
342
|
collection: slug,
|
|
139
343
|
depth: 2,
|
|
140
344
|
id: mention.id,
|
|
345
|
+
...activeLocale ? {
|
|
346
|
+
locale: activeLocale
|
|
347
|
+
} : {},
|
|
141
348
|
overrideAccess: false,
|
|
142
349
|
req
|
|
143
350
|
}).catch(()=>null);
|
|
@@ -148,6 +355,9 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, global
|
|
|
148
355
|
doc,
|
|
149
356
|
id: mention.id,
|
|
150
357
|
label: mention.label || mention.id,
|
|
358
|
+
...activeLocale ? {
|
|
359
|
+
locale: activeLocale
|
|
360
|
+
} : {},
|
|
151
361
|
type: "doc"
|
|
152
362
|
});
|
|
153
363
|
}
|
|
@@ -157,9 +367,8 @@ export const getMentionContext = async ({ blockContexts, collectionSlugs, global
|
|
|
157
367
|
export const buildPromptWithMentionContext = ({ mentionContext, prompt })=>{
|
|
158
368
|
if (mentionContext.length === 0) return prompt;
|
|
159
369
|
return [
|
|
160
|
-
"
|
|
161
|
-
JSON.stringify(mentionContext
|
|
162
|
-
"User request:",
|
|
370
|
+
"Selected Payload references JSON. Treat inline reference labels as these entities:",
|
|
371
|
+
JSON.stringify(mentionContext),
|
|
163
372
|
prompt
|
|
164
373
|
].join("\n\n");
|
|
165
374
|
};
|
package/dist/payload/shared.d.ts
CHANGED
|
@@ -1,2 +1,35 @@
|
|
|
1
|
+
import type { PayloadHandler } from "payload";
|
|
2
|
+
export type LocalizedDataInput = Record<string, Record<string, unknown>>;
|
|
3
|
+
export type AIActionProposalLike = {
|
|
4
|
+
action: "create" | "delete" | "update" | "updateGlobal";
|
|
5
|
+
collection?: string;
|
|
6
|
+
data?: Record<string, unknown>;
|
|
7
|
+
id?: string;
|
|
8
|
+
label: string;
|
|
9
|
+
locale?: string;
|
|
10
|
+
localizedData?: LocalizedDataInput;
|
|
11
|
+
slug?: string;
|
|
12
|
+
};
|
|
1
13
|
export declare const getSerializableLabel: (label: unknown, fallback?: string) => string;
|
|
2
14
|
export declare const isInternalCollection: (slug: string) => boolean;
|
|
15
|
+
export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
|
|
16
|
+
export declare const mergeData: (current: Record<string, unknown>, next: Record<string, unknown>) => Record<string, unknown>;
|
|
17
|
+
export declare const isKnownGlobal: (req: Parameters<PayloadHandler>[0], slug: string) => boolean;
|
|
18
|
+
export declare const hasLocalizedData: <Value>(value: Value) => value is Value & {
|
|
19
|
+
localizedData: LocalizedDataInput;
|
|
20
|
+
};
|
|
21
|
+
export declare const getDefaultLocale: (req: Parameters<PayloadHandler>[0]) => string | null;
|
|
22
|
+
export declare const getJSONLineKey: (line: string) => string | null;
|
|
23
|
+
export declare const isActionProposal: (proposal: unknown) => proposal is AIActionProposalLike;
|
|
24
|
+
export declare const getOptionalNumber: (value: unknown) => number | undefined;
|
|
25
|
+
export declare const getNumber: (value: unknown) => number;
|
|
26
|
+
export declare const getString: (value: unknown) => string | null;
|
|
27
|
+
export declare const getDocLabel: (doc: Record<string, unknown>, useAsTitle?: string) => string;
|
|
28
|
+
export declare const getOptionValue: (option?: string | {
|
|
29
|
+
label?: unknown;
|
|
30
|
+
value?: string;
|
|
31
|
+
}) => string | undefined;
|
|
32
|
+
export declare const hasValueAtPath: (data: Record<string, unknown>, path: string) => boolean;
|
|
33
|
+
export declare const getSafeProposalLabel: (label: string) => string;
|
|
34
|
+
export declare const setValueAtPath: (data: Record<string, unknown>, path: string, value: unknown) => void;
|
|
35
|
+
export declare const isAbortError: (err: unknown) => boolean;
|
package/dist/payload/shared.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const maxProposalLabelLength = 90;
|
|
1
2
|
export const getSerializableLabel = (label, fallback = "")=>{
|
|
2
3
|
if (typeof label === "string") return label;
|
|
3
4
|
if (label && typeof label === "object") {
|
|
@@ -9,3 +10,116 @@ export const getSerializableLabel = (label, fallback = "")=>{
|
|
|
9
10
|
export const isInternalCollection = (slug)=>{
|
|
10
11
|
return slug.startsWith("payload-") || slug === "plugin-collection";
|
|
11
12
|
};
|
|
13
|
+
export const isRecord = (value)=>{
|
|
14
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
};
|
|
16
|
+
export const mergeData = (current, next)=>{
|
|
17
|
+
return Object.fromEntries(Object.entries({
|
|
18
|
+
...current,
|
|
19
|
+
...next
|
|
20
|
+
}).map(([key, value])=>{
|
|
21
|
+
const currentValue = current[key];
|
|
22
|
+
if (isRecord(currentValue) && isRecord(value)) {
|
|
23
|
+
return [
|
|
24
|
+
key,
|
|
25
|
+
mergeData(currentValue, value)
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
return [
|
|
29
|
+
key,
|
|
30
|
+
value
|
|
31
|
+
];
|
|
32
|
+
}));
|
|
33
|
+
};
|
|
34
|
+
export const isKnownGlobal = (req, slug)=>{
|
|
35
|
+
return req.payload.config.globals?.some((item)=>item.slug === slug) || false;
|
|
36
|
+
};
|
|
37
|
+
export const hasLocalizedData = (value)=>{
|
|
38
|
+
return isRecord(value) && isRecord(value.localizedData);
|
|
39
|
+
};
|
|
40
|
+
export const getDefaultLocale = (req)=>{
|
|
41
|
+
const localization = req.payload.config.localization;
|
|
42
|
+
if (!localization) return null;
|
|
43
|
+
return localization.defaultLocale || null;
|
|
44
|
+
};
|
|
45
|
+
export const getJSONLineKey = (line)=>{
|
|
46
|
+
const match = /^(\s*)"([^"]+)":/.exec(line);
|
|
47
|
+
return match ? `${match[1]}${match[2]}` : null;
|
|
48
|
+
};
|
|
49
|
+
const isNonEmptyString = (value)=>{
|
|
50
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
51
|
+
};
|
|
52
|
+
const hasWriteData = (proposal)=>{
|
|
53
|
+
const hasData = isRecord(proposal.data);
|
|
54
|
+
const hasLocalizedProposalData = hasLocalizedData(proposal);
|
|
55
|
+
return hasData !== hasLocalizedProposalData;
|
|
56
|
+
};
|
|
57
|
+
export const isActionProposal = (proposal)=>{
|
|
58
|
+
if (!isRecord(proposal) || !isNonEmptyString(proposal.label)) return false;
|
|
59
|
+
if (proposal.action === "create") {
|
|
60
|
+
return isNonEmptyString(proposal.collection) && hasWriteData(proposal);
|
|
61
|
+
}
|
|
62
|
+
if (proposal.action === "update") {
|
|
63
|
+
return isNonEmptyString(proposal.collection) && isNonEmptyString(proposal.id) && hasWriteData(proposal);
|
|
64
|
+
}
|
|
65
|
+
if (proposal.action === "delete") {
|
|
66
|
+
return isNonEmptyString(proposal.collection) && isNonEmptyString(proposal.id);
|
|
67
|
+
}
|
|
68
|
+
if (proposal.action === "updateGlobal") {
|
|
69
|
+
return isNonEmptyString(proposal.slug) && hasWriteData(proposal);
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
};
|
|
73
|
+
export const getOptionalNumber = (value)=>{
|
|
74
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
75
|
+
};
|
|
76
|
+
export const getNumber = (value)=>{
|
|
77
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
78
|
+
};
|
|
79
|
+
export const getString = (value)=>{
|
|
80
|
+
return typeof value === "string" ? value : null;
|
|
81
|
+
};
|
|
82
|
+
export const getDocLabel = (doc, useAsTitle)=>{
|
|
83
|
+
const titleField = useAsTitle ? getSerializableLabel(doc[useAsTitle]) : "";
|
|
84
|
+
const title = getSerializableLabel(doc.title);
|
|
85
|
+
const name = getSerializableLabel(doc.name);
|
|
86
|
+
const email = getSerializableLabel(doc.email);
|
|
87
|
+
return titleField || title || name || email || doc.id?.toString() || "Untitled";
|
|
88
|
+
};
|
|
89
|
+
export const getOptionValue = (option)=>{
|
|
90
|
+
if (!option) return undefined;
|
|
91
|
+
if (typeof option === "string") return option;
|
|
92
|
+
return typeof option.value === "string" ? option.value : undefined;
|
|
93
|
+
};
|
|
94
|
+
export const hasValueAtPath = (data, path)=>{
|
|
95
|
+
const segments = path.split(".");
|
|
96
|
+
let current = data;
|
|
97
|
+
for (const segment of segments){
|
|
98
|
+
if (!isRecord(current) || current[segment] === undefined) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
current = current[segment];
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
};
|
|
105
|
+
export const getSafeProposalLabel = (label)=>{
|
|
106
|
+
const firstLine = label.replace(/[*_`>#-]/g, "").split("\n").map((line)=>line.trim()).find(Boolean);
|
|
107
|
+
if (!firstLine) return "Review proposed CMS change";
|
|
108
|
+
if (firstLine.length <= maxProposalLabelLength) return firstLine;
|
|
109
|
+
return `${firstLine.slice(0, maxProposalLabelLength - 3).trim()}...`;
|
|
110
|
+
};
|
|
111
|
+
export const setValueAtPath = (data, path, value)=>{
|
|
112
|
+
const segments = path.split(".");
|
|
113
|
+
let current = data;
|
|
114
|
+
for (const segment of segments.slice(0, -1)){
|
|
115
|
+
const next = current[segment];
|
|
116
|
+
if (!isRecord(next)) {
|
|
117
|
+
current[segment] = {};
|
|
118
|
+
}
|
|
119
|
+
current = current[segment];
|
|
120
|
+
}
|
|
121
|
+
current[segments[segments.length - 1]] = value;
|
|
122
|
+
};
|
|
123
|
+
export const isAbortError = (err)=>{
|
|
124
|
+
return err instanceof DOMException && err.name === "AbortError";
|
|
125
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mvriu5/payload-ai",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.10",
|
|
4
4
|
"description": "AI assistant plugin for Payload CMS with provider selection, CMS mentions, and signed action proposals.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"payload": "3.84.1",
|
|
76
76
|
"prettier": "^3.8.4",
|
|
77
77
|
"react": "19.2.7",
|
|
78
|
-
"react-dom": "19.2.
|
|
78
|
+
"react-dom": "19.2.7",
|
|
79
79
|
"rimraf": "6.1.3",
|
|
80
80
|
"sharp": "0.35.1",
|
|
81
81
|
"typescript": "5.9.3",
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
},
|
|
95
95
|
"knip": {
|
|
96
96
|
"entry": [
|
|
97
|
+
"src/exports/client.ts",
|
|
97
98
|
"dev/payload.config.ts",
|
|
98
99
|
"dev/next.config.mjs",
|
|
99
100
|
"dev/app/**/*.{ts,tsx}"
|
|
@@ -101,6 +102,9 @@
|
|
|
101
102
|
"ignore": [
|
|
102
103
|
"dev/payload-types.ts"
|
|
103
104
|
],
|
|
105
|
+
"ignoreDependencies": [
|
|
106
|
+
"prettier"
|
|
107
|
+
],
|
|
104
108
|
"project": [
|
|
105
109
|
"src/**/*.{ts,tsx}",
|
|
106
110
|
"dev/**/*.{ts,tsx,mjs}"
|
|
@@ -111,6 +115,7 @@
|
|
|
111
115
|
"@ai-sdk/google": "^3.0.80",
|
|
112
116
|
"@ai-sdk/mistral": "^3.0.37",
|
|
113
117
|
"@ai-sdk/openai": "^3.0.67",
|
|
118
|
+
"@openrouter/ai-sdk-provider": "^2.9.1",
|
|
114
119
|
"ai": "^6.0.193",
|
|
115
120
|
"zod": "^4.4.3"
|
|
116
121
|
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export type AIActionProposal = {
|
|
2
|
-
_aiSignature?: {
|
|
3
|
-
expiresAt: string;
|
|
4
|
-
value: string;
|
|
5
|
-
};
|
|
6
|
-
action: "create" | "delete" | "update" | "updateGlobal";
|
|
7
|
-
collection?: string;
|
|
8
|
-
data?: Record<string, unknown>;
|
|
9
|
-
id?: string;
|
|
10
|
-
label: string;
|
|
11
|
-
slug?: string;
|
|
12
|
-
};
|
|
13
|
-
type AIActionProposalListProps = {
|
|
14
|
-
appliedProposalIndexes: number[];
|
|
15
|
-
description?: string;
|
|
16
|
-
error?: string;
|
|
17
|
-
getViewURL?: (proposal: AIActionProposal) => string | null;
|
|
18
|
-
isApplying: boolean;
|
|
19
|
-
onDismiss?: () => void;
|
|
20
|
-
onDismissError?: () => void;
|
|
21
|
-
onApply: (proposal: AIActionProposal, index: number) => void;
|
|
22
|
-
proposals: AIActionProposal[];
|
|
23
|
-
};
|
|
24
|
-
export declare const AIActionProposalList: ({ appliedProposalIndexes, description, error, getViewURL, isApplying, onDismiss, onDismissError, onApply, proposals, }: AIActionProposalListProps) => import("react").JSX.Element | null;
|
|
25
|
-
export {};
|