@mvriu5/payload-ai 0.5.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 +99 -0
- package/dist/ai/proposals.d.ts +12 -0
- package/dist/ai/proposals.js +50 -0
- package/dist/ai/providerOptions.d.ts +85 -0
- package/dist/ai/providerOptions.js +141 -0
- package/dist/ai/providerRuntime.d.ts +18 -0
- package/dist/ai/providerRuntime.js +53 -0
- package/dist/ai/sensitiveData.d.ts +2 -0
- package/dist/ai/sensitiveData.js +30 -0
- package/dist/components/AIActionProposalList.d.ts +25 -0
- package/dist/components/AIActionProposalList.js +94 -0
- package/dist/components/AIActionProposalList.module.css +175 -0
- package/dist/components/AIApiKeyField.d.ts +2 -0
- package/dist/components/AIApiKeyField.js +55 -0
- package/dist/components/AIInput.d.ts +1 -0
- package/dist/components/AIInput.js +386 -0
- package/dist/components/AIInput.module.css +237 -0
- package/dist/components/CollectionMentionPopover.d.ts +16 -0
- package/dist/components/CollectionMentionPopover.js +77 -0
- package/dist/components/CollectionMentionPopover.module.css +67 -0
- package/dist/components/hooks/useAISettings.d.ts +10 -0
- package/dist/components/hooks/useAISettings.js +56 -0
- package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +16 -0
- package/dist/components/hooks/useDocumentMentionSuggestions.js +53 -0
- package/dist/components/hooks/utils.d.ts +1 -0
- package/dist/components/hooks/utils.js +3 -0
- package/dist/endpoints/aiApplyActionEndpointHandler.d.ts +6 -0
- package/dist/endpoints/aiApplyActionEndpointHandler.js +164 -0
- package/dist/endpoints/aiChatEndpointHandler.d.ts +31 -0
- package/dist/endpoints/aiChatEndpointHandler.js +297 -0
- package/dist/endpoints/aiMentionSuggestionsEndpointHandler.d.ts +6 -0
- package/dist/endpoints/aiMentionSuggestionsEndpointHandler.js +63 -0
- package/dist/exports/client.d.ts +2 -0
- package/dist/exports/client.js +2 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +75 -0
- package/dist/payload/normalizeData.d.ts +29 -0
- package/dist/payload/normalizeData.js +192 -0
- package/dist/payload/schemaContext.d.ts +52 -0
- package/dist/payload/schemaContext.js +162 -0
- package/dist/payload/shared.d.ts +2 -0
- package/dist/payload/shared.js +11 -0
- package/package.json +126 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type FieldConfig = {
|
|
2
|
+
blocks?: BlockConfig[];
|
|
3
|
+
defaultValue?: unknown;
|
|
4
|
+
fields?: FieldConfig[];
|
|
5
|
+
name?: string;
|
|
6
|
+
options?: (string | {
|
|
7
|
+
value?: string;
|
|
8
|
+
})[];
|
|
9
|
+
type?: string;
|
|
10
|
+
};
|
|
11
|
+
type BlockConfig = {
|
|
12
|
+
fields?: FieldConfig[];
|
|
13
|
+
slug: string;
|
|
14
|
+
};
|
|
15
|
+
export type CollectionConfig = {
|
|
16
|
+
auth?: unknown;
|
|
17
|
+
fields: FieldConfig[];
|
|
18
|
+
slug: string;
|
|
19
|
+
};
|
|
20
|
+
export type NormalizedData = {
|
|
21
|
+
coercedFields: string[];
|
|
22
|
+
data: Record<string, unknown>;
|
|
23
|
+
droppedFields: string[];
|
|
24
|
+
};
|
|
25
|
+
export declare const isAuthCollection: (collectionConfig?: CollectionConfig | null) => boolean;
|
|
26
|
+
export declare const getCollectionFields: (collectionConfig?: CollectionConfig | null) => FieldConfig[];
|
|
27
|
+
export declare const normalizeAuthData: (collectionConfig: CollectionConfig | undefined, normalized: NormalizedData) => NormalizedData;
|
|
28
|
+
export declare const normalizeDataForFields: (fields: FieldConfig[], data: Record<string, unknown>) => NormalizedData;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
const SKIP_FIELD = Symbol("skipField");
|
|
2
|
+
const isRecord = (value)=>{
|
|
3
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4
|
+
};
|
|
5
|
+
const getNamedFields = (fields)=>{
|
|
6
|
+
return fields.filter((field)=>Boolean(field.name));
|
|
7
|
+
};
|
|
8
|
+
export const isAuthCollection = (collectionConfig)=>{
|
|
9
|
+
return Boolean(collectionConfig?.auth);
|
|
10
|
+
};
|
|
11
|
+
export const getCollectionFields = (collectionConfig)=>{
|
|
12
|
+
const fields = [
|
|
13
|
+
...collectionConfig?.fields || []
|
|
14
|
+
];
|
|
15
|
+
if (isAuthCollection(collectionConfig)) {
|
|
16
|
+
fields.push({
|
|
17
|
+
name: "email",
|
|
18
|
+
type: "email"
|
|
19
|
+
}, {
|
|
20
|
+
name: "password",
|
|
21
|
+
type: "text"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return fields;
|
|
25
|
+
};
|
|
26
|
+
const createLexicalText = (value)=>{
|
|
27
|
+
if (isRecord(value) && isRecord(value.root)) return value;
|
|
28
|
+
const text = Array.isArray(value) ? value.join("\n") : String(value || "");
|
|
29
|
+
const lines = text.split("\n").map((line)=>line.trim()).filter(Boolean);
|
|
30
|
+
return {
|
|
31
|
+
root: {
|
|
32
|
+
children: (lines.length ? lines : [
|
|
33
|
+
""
|
|
34
|
+
]).map((line)=>({
|
|
35
|
+
children: [
|
|
36
|
+
{
|
|
37
|
+
detail: 0,
|
|
38
|
+
format: 0,
|
|
39
|
+
mode: "normal",
|
|
40
|
+
style: "",
|
|
41
|
+
text: line,
|
|
42
|
+
type: "text",
|
|
43
|
+
version: 1
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
direction: null,
|
|
47
|
+
format: "",
|
|
48
|
+
indent: 0,
|
|
49
|
+
type: "paragraph",
|
|
50
|
+
version: 1
|
|
51
|
+
})),
|
|
52
|
+
direction: null,
|
|
53
|
+
format: "",
|
|
54
|
+
indent: 0,
|
|
55
|
+
type: "root",
|
|
56
|
+
version: 1
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const normalizeArrayValue = (field, value)=>{
|
|
61
|
+
if (!Array.isArray(value)) return value;
|
|
62
|
+
const childFields = getNamedFields(field.fields || []);
|
|
63
|
+
const itemLabelField = childFields.find((childField)=>childField.name === "label") || childFields.find((childField)=>childField.name === "title") || childFields.find((childField)=>childField.name === "name") || childFields.find((childField)=>childField.name === "value") || childFields[0];
|
|
64
|
+
if (!itemLabelField) return value;
|
|
65
|
+
return value.map((item)=>{
|
|
66
|
+
if (!isRecord(item)) return {
|
|
67
|
+
[itemLabelField.name]: item
|
|
68
|
+
};
|
|
69
|
+
return normalizeDataForFields(childFields, item).data;
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
const getOptionValues = (field)=>{
|
|
73
|
+
return (field.options || []).map((option)=>typeof option === "string" ? option : option.value).filter((option)=>Boolean(option));
|
|
74
|
+
};
|
|
75
|
+
const normalizeOptionValue = (field, value)=>{
|
|
76
|
+
const optionValues = getOptionValues(field);
|
|
77
|
+
const stringValue = value === null ? "" : String(value);
|
|
78
|
+
const defaultValue = typeof field.defaultValue === "string" ? field.defaultValue : null;
|
|
79
|
+
if (stringValue && optionValues.includes(stringValue)) return stringValue;
|
|
80
|
+
if (defaultValue && optionValues.includes(defaultValue)) return defaultValue;
|
|
81
|
+
return SKIP_FIELD;
|
|
82
|
+
};
|
|
83
|
+
const normalizeBlocksValue = (field, value)=>{
|
|
84
|
+
if (!Array.isArray(value)) return value;
|
|
85
|
+
return value.map((item)=>{
|
|
86
|
+
if (!isRecord(item)) return null;
|
|
87
|
+
const blockType = typeof item.blockType === "string" ? item.blockType : typeof item.type === "string" ? item.type : typeof item.slug === "string" ? item.slug : null;
|
|
88
|
+
if (!blockType) return null;
|
|
89
|
+
const block = field.blocks?.find((candidate)=>candidate.slug === blockType);
|
|
90
|
+
if (!block) return null;
|
|
91
|
+
const { blockType: _blockType, type: _type, slug: _slug, ...data } = item;
|
|
92
|
+
const normalizedBlock = normalizeDataForFields(block.fields || [], data).data;
|
|
93
|
+
return {
|
|
94
|
+
...normalizedBlock,
|
|
95
|
+
blockType
|
|
96
|
+
};
|
|
97
|
+
}).filter(Boolean);
|
|
98
|
+
};
|
|
99
|
+
const normalizeFieldValue = (field, value)=>{
|
|
100
|
+
if (value === undefined) return value;
|
|
101
|
+
if (field.type === "array") return normalizeArrayValue(field, value);
|
|
102
|
+
if (field.type === "blocks") return normalizeBlocksValue(field, value);
|
|
103
|
+
if (field.type === "checkbox") return typeof value === "boolean" ? value : value === "true";
|
|
104
|
+
if (field.type === "group" && isRecord(value)) return normalizeDataForFields(field.fields || [], value).data;
|
|
105
|
+
if (field.type === "richText") return createLexicalText(value);
|
|
106
|
+
if ([
|
|
107
|
+
"radio",
|
|
108
|
+
"select"
|
|
109
|
+
].includes(field.type || "")) return normalizeOptionValue(field, value);
|
|
110
|
+
if ([
|
|
111
|
+
"email",
|
|
112
|
+
"text",
|
|
113
|
+
"textarea"
|
|
114
|
+
].includes(field.type || "")) return value === null ? value : String(value);
|
|
115
|
+
if (field.type === "date") {
|
|
116
|
+
const date = new Date(String(value));
|
|
117
|
+
return Number.isNaN(date.getTime()) ? value : date.toISOString();
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
};
|
|
121
|
+
export const normalizeAuthData = (collectionConfig, normalized)=>{
|
|
122
|
+
if (!isAuthCollection(collectionConfig)) return normalized;
|
|
123
|
+
const email = normalized.data.email;
|
|
124
|
+
const password = normalized.data.password;
|
|
125
|
+
if (email !== undefined && typeof email !== "string") {
|
|
126
|
+
normalized.data.email = String(email);
|
|
127
|
+
normalized.coercedFields.push("email");
|
|
128
|
+
}
|
|
129
|
+
if (password !== undefined && typeof password !== "string") {
|
|
130
|
+
normalized.data.password = String(password);
|
|
131
|
+
normalized.coercedFields.push("password");
|
|
132
|
+
}
|
|
133
|
+
if (typeof normalized.data.password === "string" && normalized.data.password.length > 0 && normalized.data.password.length < 8) {
|
|
134
|
+
throw new Error("Password must be at least 8 characters long.");
|
|
135
|
+
}
|
|
136
|
+
return normalized;
|
|
137
|
+
};
|
|
138
|
+
const getAliasFieldName = (key, fieldsByName)=>{
|
|
139
|
+
if (fieldsByName.has(key)) return key;
|
|
140
|
+
if (key.endsWith("Date")) {
|
|
141
|
+
const dateAlias = `${key.slice(0, -4)}At`;
|
|
142
|
+
if (fieldsByName.has(dateAlias)) return dateAlias;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
};
|
|
146
|
+
const normalizeLooseKnownFieldValue = (key, value)=>{
|
|
147
|
+
if (key === "tags" && Array.isArray(value)) {
|
|
148
|
+
return value.map((item)=>isRecord(item) ? item : {
|
|
149
|
+
label: item
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return value;
|
|
153
|
+
};
|
|
154
|
+
export const normalizeDataForFields = (fields, data)=>{
|
|
155
|
+
const namedFields = getNamedFields(fields);
|
|
156
|
+
const fieldsByName = new Map(namedFields.map((field)=>[
|
|
157
|
+
field.name,
|
|
158
|
+
field
|
|
159
|
+
]));
|
|
160
|
+
const normalizedData = {};
|
|
161
|
+
const droppedFields = [];
|
|
162
|
+
const coercedFields = [];
|
|
163
|
+
for (const [key, value] of Object.entries(data)){
|
|
164
|
+
const fieldName = getAliasFieldName(key, fieldsByName);
|
|
165
|
+
if (!fieldName) {
|
|
166
|
+
const looseValue = normalizeLooseKnownFieldValue(key, value);
|
|
167
|
+
if (looseValue !== value) {
|
|
168
|
+
normalizedData[key] = looseValue;
|
|
169
|
+
coercedFields.push(key);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
droppedFields.push(key);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const field = fieldsByName.get(fieldName);
|
|
176
|
+
if (!field) continue;
|
|
177
|
+
const normalizedValue = normalizeFieldValue(field, value);
|
|
178
|
+
if (normalizedValue === SKIP_FIELD) {
|
|
179
|
+
droppedFields.push(key);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (fieldName !== key || normalizedValue !== value) {
|
|
183
|
+
coercedFields.push(key);
|
|
184
|
+
}
|
|
185
|
+
normalizedData[fieldName] = normalizedValue;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
coercedFields,
|
|
189
|
+
data: normalizedData,
|
|
190
|
+
droppedFields
|
|
191
|
+
};
|
|
192
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { PayloadHandler } from "payload";
|
|
2
|
+
export type AIChatMention = {
|
|
3
|
+
collection?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
parent?: string;
|
|
7
|
+
slug?: string;
|
|
8
|
+
type?: "block" | "collection" | "doc" | "global";
|
|
9
|
+
};
|
|
10
|
+
export type FieldConfig = {
|
|
11
|
+
blocks?: BlockConfig[];
|
|
12
|
+
fields?: FieldConfig[];
|
|
13
|
+
hasMany?: boolean;
|
|
14
|
+
label?: unknown;
|
|
15
|
+
name?: string;
|
|
16
|
+
relationTo?: unknown;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
type?: string;
|
|
19
|
+
};
|
|
20
|
+
type BlockConfig = {
|
|
21
|
+
fields?: FieldConfig[];
|
|
22
|
+
labels?: {
|
|
23
|
+
plural?: unknown;
|
|
24
|
+
singular?: unknown;
|
|
25
|
+
};
|
|
26
|
+
slug: string;
|
|
27
|
+
};
|
|
28
|
+
type MentionContext = {
|
|
29
|
+
blockContexts: (Record<string, unknown> & {
|
|
30
|
+
parent: string;
|
|
31
|
+
slug: string;
|
|
32
|
+
})[];
|
|
33
|
+
collectionSlugs: string[];
|
|
34
|
+
globalSlugs: string[];
|
|
35
|
+
mentions?: AIChatMention[];
|
|
36
|
+
req: Parameters<PayloadHandler>[0];
|
|
37
|
+
};
|
|
38
|
+
export declare const describeField: (field: FieldConfig) => Record<string, unknown>;
|
|
39
|
+
export declare const collectBlocks: ({ fields, parent, }: {
|
|
40
|
+
fields: FieldConfig[];
|
|
41
|
+
parent: string;
|
|
42
|
+
}) => (Record<string, unknown> & {
|
|
43
|
+
parent: string;
|
|
44
|
+
slug: string;
|
|
45
|
+
})[];
|
|
46
|
+
export declare const getAllowedCollectionSlugs: (req: Parameters<PayloadHandler>[0], collections?: string[]) => string[];
|
|
47
|
+
export declare const getMentionContext: ({ blockContexts, collectionSlugs, globalSlugs, mentions, req, }: MentionContext) => Promise<Record<string, unknown>[]>;
|
|
48
|
+
export declare const buildPromptWithMentionContext: ({ mentionContext, prompt, }: {
|
|
49
|
+
mentionContext: Record<string, unknown>[];
|
|
50
|
+
prompt: string;
|
|
51
|
+
}) => string;
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { getSerializableLabel, isInternalCollection } from "./shared.js";
|
|
2
|
+
const getSerializableRelationTo = (relationTo)=>{
|
|
3
|
+
if (typeof relationTo === "string") {
|
|
4
|
+
return relationTo;
|
|
5
|
+
}
|
|
6
|
+
if (Array.isArray(relationTo) && relationTo.every((item)=>typeof item === "string")) {
|
|
7
|
+
return relationTo;
|
|
8
|
+
}
|
|
9
|
+
return undefined;
|
|
10
|
+
};
|
|
11
|
+
export const describeField = (field)=>{
|
|
12
|
+
const label = getSerializableLabel(field.label);
|
|
13
|
+
const relationTo = getSerializableRelationTo(field.relationTo);
|
|
14
|
+
return {
|
|
15
|
+
...label ? {
|
|
16
|
+
label
|
|
17
|
+
} : {},
|
|
18
|
+
...field.name ? {
|
|
19
|
+
name: field.name
|
|
20
|
+
} : {},
|
|
21
|
+
...field.type ? {
|
|
22
|
+
type: field.type
|
|
23
|
+
} : {},
|
|
24
|
+
...field.required ? {
|
|
25
|
+
required: field.required
|
|
26
|
+
} : {},
|
|
27
|
+
...field.hasMany ? {
|
|
28
|
+
hasMany: field.hasMany
|
|
29
|
+
} : {},
|
|
30
|
+
...relationTo ? {
|
|
31
|
+
relationTo
|
|
32
|
+
} : {},
|
|
33
|
+
...field.fields ? {
|
|
34
|
+
fields: field.fields.map(describeField)
|
|
35
|
+
} : {},
|
|
36
|
+
...field.blocks ? {
|
|
37
|
+
blocks: field.blocks.map(describeBlock)
|
|
38
|
+
} : {}
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
const describeBlock = (block)=>{
|
|
42
|
+
return {
|
|
43
|
+
fields: (block.fields || []).map(describeField),
|
|
44
|
+
label: getSerializableLabel(block.labels?.singular) || block.slug,
|
|
45
|
+
slug: block.slug
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
export const collectBlocks = ({ fields, parent })=>{
|
|
49
|
+
const blocks = [];
|
|
50
|
+
for (const field of fields){
|
|
51
|
+
if (field.type === "blocks" && field.blocks) {
|
|
52
|
+
for (const block of field.blocks){
|
|
53
|
+
blocks.push({
|
|
54
|
+
...describeBlock(block),
|
|
55
|
+
parent,
|
|
56
|
+
slug: block.slug
|
|
57
|
+
});
|
|
58
|
+
blocks.push(...collectBlocks({
|
|
59
|
+
fields: block.fields || [],
|
|
60
|
+
parent: `${parent}/${block.slug}`
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (field.fields) {
|
|
65
|
+
blocks.push(...collectBlocks({
|
|
66
|
+
fields: field.fields,
|
|
67
|
+
parent
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return blocks;
|
|
72
|
+
};
|
|
73
|
+
export const getAllowedCollectionSlugs = (req, collections)=>{
|
|
74
|
+
const configuredSlugs = req.payload.config.collections.map((collection)=>collection.slug).filter((slug)=>!isInternalCollection(slug));
|
|
75
|
+
if (!collections) return configuredSlugs;
|
|
76
|
+
return configuredSlugs.filter((slug)=>collections.includes(slug));
|
|
77
|
+
};
|
|
78
|
+
export const getMentionContext = async ({ blockContexts, collectionSlugs, globalSlugs, mentions, req })=>{
|
|
79
|
+
if (!mentions || mentions.length === 0) return [];
|
|
80
|
+
const context = [];
|
|
81
|
+
const seen = new Set();
|
|
82
|
+
for (const mention of mentions.slice(0, 8)){
|
|
83
|
+
if (mention.type === "collection" && mention.slug) {
|
|
84
|
+
const slug = mention.slug;
|
|
85
|
+
const key = `collection:${slug}`;
|
|
86
|
+
if (seen.has(key) || isInternalCollection(slug) || !collectionSlugs.includes(slug)) continue;
|
|
87
|
+
const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === slug);
|
|
88
|
+
if (!collectionConfig) continue;
|
|
89
|
+
seen.add(key);
|
|
90
|
+
context.push({
|
|
91
|
+
fields: collectionConfig.fields.map(describeField),
|
|
92
|
+
label: collectionConfig.labels?.plural || collectionConfig.labels?.singular || slug,
|
|
93
|
+
slug,
|
|
94
|
+
type: "collection"
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (mention.type === "global" && mention.slug) {
|
|
98
|
+
const slug = mention.slug;
|
|
99
|
+
const key = `global:${slug}`;
|
|
100
|
+
if (seen.has(key) || !globalSlugs.includes(slug)) continue;
|
|
101
|
+
const globalConfig = req.payload.config.globals?.find((global)=>global.slug === slug);
|
|
102
|
+
if (!globalConfig) continue;
|
|
103
|
+
const globalDoc = await req.payload.findGlobal({
|
|
104
|
+
depth: 2,
|
|
105
|
+
overrideAccess: false,
|
|
106
|
+
req,
|
|
107
|
+
slug: slug
|
|
108
|
+
}).catch(()=>null);
|
|
109
|
+
seen.add(key);
|
|
110
|
+
context.push({
|
|
111
|
+
doc: globalDoc,
|
|
112
|
+
fields: globalConfig.fields.map(describeField),
|
|
113
|
+
label: globalConfig.label || slug,
|
|
114
|
+
slug,
|
|
115
|
+
type: "global"
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (mention.type === "block" && mention.slug) {
|
|
119
|
+
const matchingBlocks = blockContexts.filter((block)=>block.slug === mention.slug && (!mention.parent || block.parent === mention.parent));
|
|
120
|
+
for (const block of matchingBlocks){
|
|
121
|
+
const key = `block:${block.parent}:${block.slug}`;
|
|
122
|
+
if (seen.has(key)) continue;
|
|
123
|
+
seen.add(key);
|
|
124
|
+
context.push({
|
|
125
|
+
...block,
|
|
126
|
+
type: "block"
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (mention.type === "doc" && mention.collection && mention.id) {
|
|
131
|
+
const slug = mention.collection;
|
|
132
|
+
const key = `doc:${slug}:${mention.id}`;
|
|
133
|
+
if (seen.has(key) || isInternalCollection(slug) || !collectionSlugs.includes(slug)) continue;
|
|
134
|
+
const doc = await req.payload.findByID({
|
|
135
|
+
collection: slug,
|
|
136
|
+
depth: 2,
|
|
137
|
+
id: mention.id,
|
|
138
|
+
overrideAccess: false,
|
|
139
|
+
req
|
|
140
|
+
}).catch(()=>null);
|
|
141
|
+
if (!doc) continue;
|
|
142
|
+
seen.add(key);
|
|
143
|
+
context.push({
|
|
144
|
+
collection: slug,
|
|
145
|
+
doc,
|
|
146
|
+
id: mention.id,
|
|
147
|
+
label: mention.label || mention.id,
|
|
148
|
+
type: "doc"
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return context;
|
|
153
|
+
};
|
|
154
|
+
export const buildPromptWithMentionContext = ({ mentionContext, prompt })=>{
|
|
155
|
+
if (mentionContext.length === 0) return prompt;
|
|
156
|
+
return [
|
|
157
|
+
"The user selected the following Payload CMS references in the input. Treat inline text like `collection: Name` or `document: Name` as references to this context, not as literal content.",
|
|
158
|
+
JSON.stringify(mentionContext, null, 2),
|
|
159
|
+
"User request:",
|
|
160
|
+
prompt
|
|
161
|
+
].join("\n\n");
|
|
162
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const getSerializableLabel = (label, fallback = "")=>{
|
|
2
|
+
if (typeof label === "string") return label;
|
|
3
|
+
if (label && typeof label === "object") {
|
|
4
|
+
const firstLabel = Object.values(label).find((value)=>typeof value === "string");
|
|
5
|
+
if (typeof firstLabel === "string") return firstLabel;
|
|
6
|
+
}
|
|
7
|
+
return fallback;
|
|
8
|
+
};
|
|
9
|
+
export const isInternalCollection = (slug)=>{
|
|
10
|
+
return slug.startsWith("payload-") || slug === "plugin-collection";
|
|
11
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mvriu5/payload-ai",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "AI assistant plugin for Payload CMS with provider selection, CMS mentions, and signed action proposals.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"payload",
|
|
7
|
+
"payloadcms",
|
|
8
|
+
"payload-plugin",
|
|
9
|
+
"ai",
|
|
10
|
+
"cms",
|
|
11
|
+
"openai",
|
|
12
|
+
"anthropic",
|
|
13
|
+
"google-generative-ai",
|
|
14
|
+
"groq",
|
|
15
|
+
"mistral"
|
|
16
|
+
],
|
|
17
|
+
"author": "Marius",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"homepage": "https://github.com/mvriu5/payload-ai-plugin#readme",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/mvriu5/payload-ai-plugin.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/mvriu5/payload-ai-plugin/issues"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./client": {
|
|
35
|
+
"import": "./dist/exports/client.js",
|
|
36
|
+
"types": "./dist/exports/client.d.ts",
|
|
37
|
+
"default": "./dist/exports/client.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"main": "./dist/index.js",
|
|
41
|
+
"types": "./dist/index.d.ts",
|
|
42
|
+
"files": [
|
|
43
|
+
"dist"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
|
47
|
+
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
48
|
+
"build:types": "tsc --outDir dist --rootDir ./src",
|
|
49
|
+
"clean": "rimraf {dist,*.tsbuildinfo}",
|
|
50
|
+
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
|
51
|
+
"dev": "next dev dev --turbo",
|
|
52
|
+
"dev:generate-importmap": "pnpm dev:payload generate:importmap",
|
|
53
|
+
"dev:generate-types": "pnpm dev:payload generate:types",
|
|
54
|
+
"dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
|
|
55
|
+
"generate:importmap": "pnpm dev:generate-importmap",
|
|
56
|
+
"generate:types": "pnpm dev:generate-types",
|
|
57
|
+
"lint": "eslint",
|
|
58
|
+
"lint:fix": "eslint ./src --fix",
|
|
59
|
+
"prepublishOnly": "pnpm clean && pnpm build"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@payloadcms/db-postgres": "3.84.1",
|
|
63
|
+
"@payloadcms/next": "3.84.1",
|
|
64
|
+
"@payloadcms/richtext-lexical": "3.84.1",
|
|
65
|
+
"@payloadcms/ui": "3.84.1",
|
|
66
|
+
"@swc/cli": "0.6.0",
|
|
67
|
+
"@types/node": "22.19.9",
|
|
68
|
+
"@types/react": "19.2.14",
|
|
69
|
+
"@types/react-dom": "19.2.3",
|
|
70
|
+
"copyfiles": "2.4.1",
|
|
71
|
+
"cross-env": "^7.0.3",
|
|
72
|
+
"eslint": "^9.23.0",
|
|
73
|
+
"graphql": "^16.8.1",
|
|
74
|
+
"knip": "^6.16.1",
|
|
75
|
+
"next": "16.2.6",
|
|
76
|
+
"payload": "3.84.1",
|
|
77
|
+
"react": "19.2.6",
|
|
78
|
+
"react-dom": "19.2.6",
|
|
79
|
+
"rimraf": "3.0.2",
|
|
80
|
+
"sharp": "0.34.2",
|
|
81
|
+
"typescript": "5.7.3",
|
|
82
|
+
"vite-tsconfig-paths": "6.0.5",
|
|
83
|
+
"vitest": "4.0.18"
|
|
84
|
+
},
|
|
85
|
+
"peerDependencies": {
|
|
86
|
+
"payload": "^3.84.1"
|
|
87
|
+
},
|
|
88
|
+
"engines": {
|
|
89
|
+
"node": "^18.20.2 || >=20.9.0",
|
|
90
|
+
"pnpm": "^9 || ^10"
|
|
91
|
+
},
|
|
92
|
+
"publishConfig": {
|
|
93
|
+
"access": "public"
|
|
94
|
+
},
|
|
95
|
+
"pnpm": {
|
|
96
|
+
"onlyBuiltDependencies": [
|
|
97
|
+
"sharp",
|
|
98
|
+
"esbuild",
|
|
99
|
+
"unrs-resolver"
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
"registry": "https://registry.npmjs.org/",
|
|
103
|
+
"knip": {
|
|
104
|
+
"entry": [
|
|
105
|
+
"dev/payload.config.ts",
|
|
106
|
+
"dev/next.config.mjs",
|
|
107
|
+
"dev/app/**/*.{ts,tsx}"
|
|
108
|
+
],
|
|
109
|
+
"ignore": [
|
|
110
|
+
"dev/payload-types.ts"
|
|
111
|
+
],
|
|
112
|
+
"project": [
|
|
113
|
+
"src/**/*.{ts,tsx}",
|
|
114
|
+
"dev/**/*.{ts,tsx,mjs}"
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
"dependencies": {
|
|
118
|
+
"@ai-sdk/anthropic": "^3.0.81",
|
|
119
|
+
"@ai-sdk/google": "^3.0.80",
|
|
120
|
+
"@ai-sdk/groq": "^3.0.39",
|
|
121
|
+
"@ai-sdk/mistral": "^3.0.37",
|
|
122
|
+
"@ai-sdk/openai": "^3.0.67",
|
|
123
|
+
"ai": "^6.0.193",
|
|
124
|
+
"zod": "^4.4.3"
|
|
125
|
+
}
|
|
126
|
+
}
|