@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,16 +0,0 @@
|
|
|
1
|
-
import type { RefObject } from "react";
|
|
2
|
-
export type CollectionMentionOption = {
|
|
3
|
-
collection?: string;
|
|
4
|
-
id?: string;
|
|
5
|
-
label: string;
|
|
6
|
-
parent?: string;
|
|
7
|
-
slug: string;
|
|
8
|
-
type: "block" | "collection" | "doc" | "global";
|
|
9
|
-
};
|
|
10
|
-
type CollectionMentionPopoverProps = {
|
|
11
|
-
containerRef?: RefObject<HTMLDivElement | null>;
|
|
12
|
-
suggestions: CollectionMentionOption[];
|
|
13
|
-
onSelect: (suggestion: CollectionMentionOption) => void;
|
|
14
|
-
};
|
|
15
|
-
export declare const CollectionMentionPopover: ({ containerRef, onSelect, suggestions }: CollectionMentionPopoverProps) => import("react").JSX.Element | null;
|
|
16
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const isAbortError: (err: unknown) => boolean;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { PayloadHandler } from "payload";
|
|
2
|
-
import { type ResolvedAICollectionPermissionMap } from "../payload/collectionPermissions.js";
|
|
3
|
-
type AIApplyActionEndpointOptions = {
|
|
4
|
-
collections?: ResolvedAICollectionPermissionMap;
|
|
5
|
-
};
|
|
6
|
-
export declare const createAIApplyActionEndpointHandler: (options?: AIApplyActionEndpointOptions) => PayloadHandler;
|
|
7
|
-
export {};
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { verifyAIActionProposal } from "../ai/proposals.js";
|
|
2
|
-
import { containsSensitiveData } from "../ai/sensitiveData.js";
|
|
3
|
-
import { getCollectionFields, isAuthCollection, normalizeAuthData, normalizeDataForFields } from "../payload/normalizeData.js";
|
|
4
|
-
import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
|
|
5
|
-
const getProposalMeta = (proposal)=>{
|
|
6
|
-
if (!proposal) return {};
|
|
7
|
-
return {
|
|
8
|
-
action: proposal.action,
|
|
9
|
-
collection: "collection" in proposal ? proposal.collection : undefined,
|
|
10
|
-
id: "id" in proposal ? proposal.id : undefined,
|
|
11
|
-
slug: "slug" in proposal ? proposal.slug : undefined
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
const getAppliedDocReference = (doc)=>{
|
|
15
|
-
return doc?.id === undefined ? undefined : {
|
|
16
|
-
id: doc.id
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
const isKnownCollection = (req, collection)=>{
|
|
20
|
-
return req.payload.config.collections.some((item)=>item.slug === collection);
|
|
21
|
-
};
|
|
22
|
-
const isAllowedCollection = (req, collection, collections, action = "read")=>{
|
|
23
|
-
return isCollectionActionAllowed({
|
|
24
|
-
action,
|
|
25
|
-
permissions: collections,
|
|
26
|
-
req,
|
|
27
|
-
slug: collection
|
|
28
|
-
});
|
|
29
|
-
};
|
|
30
|
-
const isKnownGlobal = (req, slug)=>{
|
|
31
|
-
return req.payload.config.globals?.some((item)=>item.slug === slug) || false;
|
|
32
|
-
};
|
|
33
|
-
const isRecord = (value)=>{
|
|
34
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
35
|
-
};
|
|
36
|
-
const isActionProposal = (proposal)=>{
|
|
37
|
-
if (!isRecord(proposal) || typeof proposal.label !== "string") return false;
|
|
38
|
-
if (proposal.action === "create") {
|
|
39
|
-
return typeof proposal.collection === "string" && isRecord(proposal.data);
|
|
40
|
-
}
|
|
41
|
-
if (proposal.action === "update") {
|
|
42
|
-
return typeof proposal.collection === "string" && typeof proposal.id === "string" && isRecord(proposal.data);
|
|
43
|
-
}
|
|
44
|
-
if (proposal.action === "delete") {
|
|
45
|
-
return typeof proposal.collection === "string" && typeof proposal.id === "string";
|
|
46
|
-
}
|
|
47
|
-
if (proposal.action === "updateGlobal") {
|
|
48
|
-
return typeof proposal.slug === "string" && isRecord(proposal.data);
|
|
49
|
-
}
|
|
50
|
-
return false;
|
|
51
|
-
};
|
|
52
|
-
export const createAIApplyActionEndpointHandler = (options = {})=>async (req)=>{
|
|
53
|
-
if (!req.user) return Response.json({
|
|
54
|
-
error: "Unauthorized"
|
|
55
|
-
}, {
|
|
56
|
-
status: 401
|
|
57
|
-
});
|
|
58
|
-
const body = req.json ? await req.json().catch(()=>null) : null;
|
|
59
|
-
const proposal = body?.proposal;
|
|
60
|
-
if (!proposal) return Response.json({
|
|
61
|
-
error: "Proposal is required"
|
|
62
|
-
}, {
|
|
63
|
-
status: 400
|
|
64
|
-
});
|
|
65
|
-
if (!verifyAIActionProposal(proposal)) return Response.json({
|
|
66
|
-
error: "Proposal signature is invalid or expired."
|
|
67
|
-
}, {
|
|
68
|
-
status: 400
|
|
69
|
-
});
|
|
70
|
-
if (!isActionProposal(proposal)) return Response.json({
|
|
71
|
-
error: "Proposal is invalid."
|
|
72
|
-
}, {
|
|
73
|
-
status: 400
|
|
74
|
-
});
|
|
75
|
-
if ("data" in proposal && containsSensitiveData(proposal.data)) return Response.json({
|
|
76
|
-
error: "Proposal contains sensitive fields and cannot be applied."
|
|
77
|
-
}, {
|
|
78
|
-
status: 400
|
|
79
|
-
});
|
|
80
|
-
let normalized;
|
|
81
|
-
try {
|
|
82
|
-
if (proposal.action === "updateGlobal") {
|
|
83
|
-
if (!isKnownGlobal(req, proposal.slug)) return Response.json({
|
|
84
|
-
error: "Unknown global"
|
|
85
|
-
}, {
|
|
86
|
-
status: 400
|
|
87
|
-
});
|
|
88
|
-
const globalConfig = req.payload.config.globals?.find((global)=>global.slug === proposal.slug);
|
|
89
|
-
normalized = normalizeDataForFields(globalConfig?.fields || [], proposal.data);
|
|
90
|
-
const doc = await req.payload.updateGlobal({
|
|
91
|
-
data: normalized.data,
|
|
92
|
-
overrideAccess: false,
|
|
93
|
-
req,
|
|
94
|
-
slug: proposal.slug
|
|
95
|
-
});
|
|
96
|
-
return Response.json({
|
|
97
|
-
doc: getAppliedDocReference(doc),
|
|
98
|
-
status: "applied"
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
if (!isAllowedCollection(req, proposal.collection, options.collections, proposal.action)) return Response.json({
|
|
102
|
-
error: "Unknown collection"
|
|
103
|
-
}, {
|
|
104
|
-
status: 400
|
|
105
|
-
});
|
|
106
|
-
if (proposal.action === "delete") {
|
|
107
|
-
const doc = await req.payload.delete({
|
|
108
|
-
collection: proposal.collection,
|
|
109
|
-
id: proposal.id,
|
|
110
|
-
overrideAccess: false,
|
|
111
|
-
req
|
|
112
|
-
});
|
|
113
|
-
return Response.json({
|
|
114
|
-
doc: getAppliedDocReference(doc),
|
|
115
|
-
status: "applied"
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
const collectionConfig = req.payload.config.collections.find((collection)=>collection.slug === proposal.collection);
|
|
119
|
-
normalized = normalizeAuthData(collectionConfig, normalizeDataForFields(getCollectionFields(collectionConfig), proposal.data));
|
|
120
|
-
if (proposal.action === "create" && isAuthCollection(collectionConfig) && !normalized.data.password) {
|
|
121
|
-
return Response.json({
|
|
122
|
-
error: "Password is required when creating a user."
|
|
123
|
-
}, {
|
|
124
|
-
status: 400
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
if (proposal.action === "create" && isAuthCollection(collectionConfig) && !normalized.data.email) {
|
|
128
|
-
return Response.json({
|
|
129
|
-
error: "Email is required when creating a user."
|
|
130
|
-
}, {
|
|
131
|
-
status: 400
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
if (proposal.action === "create") {
|
|
135
|
-
const doc = await req.payload.create({
|
|
136
|
-
collection: proposal.collection,
|
|
137
|
-
data: normalized.data,
|
|
138
|
-
overrideAccess: false,
|
|
139
|
-
req
|
|
140
|
-
});
|
|
141
|
-
return Response.json({
|
|
142
|
-
doc: getAppliedDocReference(doc),
|
|
143
|
-
status: "applied"
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
const doc = await req.payload.update({
|
|
147
|
-
collection: proposal.collection,
|
|
148
|
-
data: normalized.data,
|
|
149
|
-
id: proposal.id,
|
|
150
|
-
overrideAccess: false,
|
|
151
|
-
req
|
|
152
|
-
});
|
|
153
|
-
return Response.json({
|
|
154
|
-
doc: getAppliedDocReference(doc),
|
|
155
|
-
status: "applied"
|
|
156
|
-
});
|
|
157
|
-
} catch (err) {
|
|
158
|
-
req.payload.logger.error({
|
|
159
|
-
err,
|
|
160
|
-
msg: "AI apply action failed",
|
|
161
|
-
proposal: getProposalMeta(proposal)
|
|
162
|
-
});
|
|
163
|
-
return Response.json({
|
|
164
|
-
error: "Could not apply proposal."
|
|
165
|
-
}, {
|
|
166
|
-
status: 400
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { PayloadHandler } from "payload";
|
|
2
|
-
import { type AIActionSignature } from "../ai/proposals.js";
|
|
3
|
-
import { type AIModelConfig } from "../ai/providerOptions.js";
|
|
4
|
-
import { type ResolvedAICollectionPermissionMap } from "../payload/collectionPermissions.js";
|
|
5
|
-
export type AIActionProposal = ({
|
|
6
|
-
action: "create";
|
|
7
|
-
collection: string;
|
|
8
|
-
data: Record<string, unknown>;
|
|
9
|
-
label: string;
|
|
10
|
-
} | {
|
|
11
|
-
action: "delete";
|
|
12
|
-
collection: string;
|
|
13
|
-
id: string;
|
|
14
|
-
label: string;
|
|
15
|
-
} | {
|
|
16
|
-
action: "update";
|
|
17
|
-
collection: string;
|
|
18
|
-
data: Record<string, unknown>;
|
|
19
|
-
id: string;
|
|
20
|
-
label: string;
|
|
21
|
-
} | {
|
|
22
|
-
action: "updateGlobal";
|
|
23
|
-
data: Record<string, unknown>;
|
|
24
|
-
label: string;
|
|
25
|
-
slug: string;
|
|
26
|
-
}) & {
|
|
27
|
-
_aiSignature?: AIActionSignature;
|
|
28
|
-
};
|
|
29
|
-
type AIChatEndpointOptions = {
|
|
30
|
-
allowUserApiKeys?: boolean;
|
|
31
|
-
collections?: ResolvedAICollectionPermissionMap;
|
|
32
|
-
models?: AIModelConfig;
|
|
33
|
-
};
|
|
34
|
-
export declare const createAIChatEndpointHandler: (options?: AIChatEndpointOptions) => PayloadHandler;
|
|
35
|
-
export {};
|
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
import { generateText, stepCountIs } from "ai";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { signAIActionProposal } from "../ai/proposals.js";
|
|
4
|
-
import { getModel, getProviderConfig } from "../ai/providerRuntime.js";
|
|
5
|
-
import { isAIProvider } from "../ai/providerOptions.js";
|
|
6
|
-
import { containsSensitiveData } from "../ai/sensitiveData.js";
|
|
7
|
-
import { buildPromptWithMentionContext, collectBlocks, describeField, getAllowedCollectionSlugs, getMentionContext } from "../payload/schemaContext.js";
|
|
8
|
-
import { getCollectionPermissions, isCollectionActionAllowed } from "../payload/collectionPermissions.js";
|
|
9
|
-
export const createAIChatEndpointHandler = (options = {})=>async (req)=>{
|
|
10
|
-
if (!req.user) return Response.json({
|
|
11
|
-
error: "Unauthorized"
|
|
12
|
-
}, {
|
|
13
|
-
status: 401
|
|
14
|
-
});
|
|
15
|
-
const body = req.json ? await req.json().catch(()=>null) : null;
|
|
16
|
-
const prompt = body?.prompt?.trim();
|
|
17
|
-
if (!prompt) return Response.json({
|
|
18
|
-
error: "Prompt is required"
|
|
19
|
-
}, {
|
|
20
|
-
status: 400
|
|
21
|
-
});
|
|
22
|
-
const user = req.user;
|
|
23
|
-
const requestedProvider = body?.provider || user.aiProvider || "openai";
|
|
24
|
-
if (!isAIProvider(requestedProvider)) return Response.json({
|
|
25
|
-
error: `Unsupported AI provider: ${requestedProvider}`
|
|
26
|
-
}, {
|
|
27
|
-
status: 400
|
|
28
|
-
});
|
|
29
|
-
const provider = requestedProvider;
|
|
30
|
-
const userApiKey = options.allowUserApiKeys === false ? null : user.aiApiKey;
|
|
31
|
-
const providerConfig = getProviderConfig({
|
|
32
|
-
apiKey: userApiKey,
|
|
33
|
-
defaultModels: options.models?.defaults,
|
|
34
|
-
model: body?.model,
|
|
35
|
-
provider
|
|
36
|
-
});
|
|
37
|
-
const debug = {
|
|
38
|
-
model: providerConfig.modelID,
|
|
39
|
-
provider,
|
|
40
|
-
tools: [
|
|
41
|
-
"getDoc",
|
|
42
|
-
"getGlobal",
|
|
43
|
-
"listCollections",
|
|
44
|
-
"listGlobals",
|
|
45
|
-
"proposeCreateDoc",
|
|
46
|
-
"proposeDeleteDoc",
|
|
47
|
-
"proposeUpdateDoc",
|
|
48
|
-
"proposeUpdateGlobal",
|
|
49
|
-
"searchDocs"
|
|
50
|
-
]
|
|
51
|
-
};
|
|
52
|
-
if (!providerConfig.apiKey) {
|
|
53
|
-
return Response.json({
|
|
54
|
-
error: options.allowUserApiKeys === false ? `Configure a ${provider} API key in the server environment first.` : `Add a ${provider} API key to your account settings or configure it in the server environment first.`
|
|
55
|
-
}, {
|
|
56
|
-
status: 400
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
const proposals = [];
|
|
61
|
-
const addSignedProposal = (proposal)=>{
|
|
62
|
-
if ("data" in proposal && containsSensitiveData(proposal.data)) {
|
|
63
|
-
return {
|
|
64
|
-
error: "Proposal contains sensitive fields and cannot be created."
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
const signedProposal = signAIActionProposal(proposal);
|
|
68
|
-
proposals.push(signedProposal);
|
|
69
|
-
return signedProposal;
|
|
70
|
-
};
|
|
71
|
-
const collectionSlugs = getAllowedCollectionSlugs(req, options.collections);
|
|
72
|
-
const globalSlugs = req.payload.config.globals?.map((global)=>global.slug) || [];
|
|
73
|
-
const allowedCollections = req.payload.config.collections.filter((collection)=>collectionSlugs.includes(collection.slug));
|
|
74
|
-
if (collectionSlugs.length === 0) {
|
|
75
|
-
return Response.json({
|
|
76
|
-
error: "No AI-enabled collections are configured."
|
|
77
|
-
}, {
|
|
78
|
-
status: 400
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
const blockContexts = [
|
|
82
|
-
...allowedCollections.flatMap((collection)=>collectBlocks({
|
|
83
|
-
fields: collection.fields,
|
|
84
|
-
parent: collection.slug
|
|
85
|
-
})),
|
|
86
|
-
...req.payload.config.globals?.flatMap((global)=>collectBlocks({
|
|
87
|
-
fields: global.fields,
|
|
88
|
-
parent: global.slug
|
|
89
|
-
})) || []
|
|
90
|
-
];
|
|
91
|
-
const mentionContext = await getMentionContext({
|
|
92
|
-
blockContexts,
|
|
93
|
-
collectionSlugs,
|
|
94
|
-
globalSlugs,
|
|
95
|
-
mentions: body?.mentions,
|
|
96
|
-
req
|
|
97
|
-
});
|
|
98
|
-
const collectionSlugSchema = z.enum(collectionSlugs);
|
|
99
|
-
const getDisallowedCollectionActionError = (collection, action)=>{
|
|
100
|
-
if (isCollectionActionAllowed({
|
|
101
|
-
action,
|
|
102
|
-
permissions: options.collections,
|
|
103
|
-
req,
|
|
104
|
-
slug: collection
|
|
105
|
-
})) return null;
|
|
106
|
-
return {
|
|
107
|
-
error: `${action} is not enabled for collection: ${collection}`
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
const tools = {
|
|
111
|
-
getDoc: {
|
|
112
|
-
description: "Read one document by collection slug and document id.",
|
|
113
|
-
inputSchema: z.object({
|
|
114
|
-
collection: collectionSlugSchema,
|
|
115
|
-
id: z.string().min(1)
|
|
116
|
-
}),
|
|
117
|
-
execute: async ({ collection, id })=>{
|
|
118
|
-
return req.payload.findByID({
|
|
119
|
-
collection: collection,
|
|
120
|
-
depth: 2,
|
|
121
|
-
id,
|
|
122
|
-
overrideAccess: false,
|
|
123
|
-
req
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
listCollections: {
|
|
128
|
-
description: "List all Payload CMS collections available in this app.",
|
|
129
|
-
inputSchema: z.object({}),
|
|
130
|
-
execute: async ()=>{
|
|
131
|
-
return allowedCollections.map((collection)=>({
|
|
132
|
-
fields: collection.fields.map(describeField),
|
|
133
|
-
label: collection.labels?.plural || collection.slug,
|
|
134
|
-
permissions: getCollectionPermissions({
|
|
135
|
-
permissions: options.collections,
|
|
136
|
-
slug: collection.slug
|
|
137
|
-
}),
|
|
138
|
-
slug: collection.slug
|
|
139
|
-
}));
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
getGlobal: {
|
|
143
|
-
description: "Read one Payload CMS global by slug.",
|
|
144
|
-
inputSchema: z.object({
|
|
145
|
-
slug: z.string().min(1)
|
|
146
|
-
}),
|
|
147
|
-
execute: async ({ slug })=>{
|
|
148
|
-
const globalConfig = req.payload.config.globals?.find((global)=>global.slug === slug);
|
|
149
|
-
if (!globalConfig) return {
|
|
150
|
-
error: `Unknown global: ${slug}`
|
|
151
|
-
};
|
|
152
|
-
return req.payload.findGlobal({
|
|
153
|
-
depth: 2,
|
|
154
|
-
overrideAccess: false,
|
|
155
|
-
req,
|
|
156
|
-
slug: slug
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
listGlobals: {
|
|
161
|
-
description: "List all Payload CMS globals available in this app.",
|
|
162
|
-
inputSchema: z.object({}),
|
|
163
|
-
execute: async ()=>{
|
|
164
|
-
return req.payload.config.globals?.map((global)=>({
|
|
165
|
-
fields: global.fields.map(describeField),
|
|
166
|
-
label: global.label || global.slug,
|
|
167
|
-
slug: global.slug
|
|
168
|
-
})) || [];
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
proposeCreateDoc: {
|
|
172
|
-
description: "Prepare a CMS document creation proposal. This does not write to the database. Use exact field names from listCollections. For array fields, provide arrays of objects matching their child fields. For richText fields, prefer plain text or omit if unsure.",
|
|
173
|
-
inputSchema: z.object({
|
|
174
|
-
collection: collectionSlugSchema,
|
|
175
|
-
data: z.record(z.string(), z.unknown()),
|
|
176
|
-
label: z.string().min(1)
|
|
177
|
-
}),
|
|
178
|
-
execute: async ({ collection, data, label })=>{
|
|
179
|
-
const permissionError = getDisallowedCollectionActionError(collection, "create");
|
|
180
|
-
if (permissionError) return permissionError;
|
|
181
|
-
const proposal = {
|
|
182
|
-
action: "create",
|
|
183
|
-
collection,
|
|
184
|
-
data,
|
|
185
|
-
label
|
|
186
|
-
};
|
|
187
|
-
return addSignedProposal(proposal);
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
proposeDeleteDoc: {
|
|
191
|
-
description: "Prepare a CMS document deletion proposal. This does not write to the database.",
|
|
192
|
-
inputSchema: z.object({
|
|
193
|
-
collection: collectionSlugSchema,
|
|
194
|
-
id: z.string().min(1),
|
|
195
|
-
label: z.string().min(1)
|
|
196
|
-
}),
|
|
197
|
-
execute: async ({ collection, id, label })=>{
|
|
198
|
-
const permissionError = getDisallowedCollectionActionError(collection, "delete");
|
|
199
|
-
if (permissionError) return permissionError;
|
|
200
|
-
const proposal = {
|
|
201
|
-
action: "delete",
|
|
202
|
-
collection,
|
|
203
|
-
id,
|
|
204
|
-
label
|
|
205
|
-
};
|
|
206
|
-
return addSignedProposal(proposal);
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
proposeUpdateDoc: {
|
|
210
|
-
description: "Prepare a CMS document update proposal. This does not write to the database. Use exact field names from listCollections. For array fields, provide arrays of objects matching their child fields. For richText fields, prefer plain text or omit if unsure.",
|
|
211
|
-
inputSchema: z.object({
|
|
212
|
-
collection: collectionSlugSchema,
|
|
213
|
-
data: z.record(z.string(), z.unknown()),
|
|
214
|
-
id: z.string().min(1),
|
|
215
|
-
label: z.string().min(1)
|
|
216
|
-
}),
|
|
217
|
-
execute: async ({ collection, data, id, label })=>{
|
|
218
|
-
const permissionError = getDisallowedCollectionActionError(collection, "update");
|
|
219
|
-
if (permissionError) return permissionError;
|
|
220
|
-
const proposal = {
|
|
221
|
-
action: "update",
|
|
222
|
-
collection,
|
|
223
|
-
data,
|
|
224
|
-
id,
|
|
225
|
-
label
|
|
226
|
-
};
|
|
227
|
-
return addSignedProposal(proposal);
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
proposeUpdateGlobal: {
|
|
231
|
-
description: "Prepare a Payload global update proposal. This does not write to the database.",
|
|
232
|
-
inputSchema: z.object({
|
|
233
|
-
data: z.record(z.string(), z.unknown()),
|
|
234
|
-
label: z.string().min(1),
|
|
235
|
-
slug: z.string().min(1)
|
|
236
|
-
}),
|
|
237
|
-
execute: async ({ data, label, slug })=>{
|
|
238
|
-
const globalConfig = req.payload.config.globals?.find((global)=>global.slug === slug);
|
|
239
|
-
if (!globalConfig) return {
|
|
240
|
-
error: `Unknown global: ${slug}`
|
|
241
|
-
};
|
|
242
|
-
const proposal = {
|
|
243
|
-
action: "updateGlobal",
|
|
244
|
-
data,
|
|
245
|
-
label,
|
|
246
|
-
slug
|
|
247
|
-
};
|
|
248
|
-
return addSignedProposal(proposal);
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
searchDocs: {
|
|
252
|
-
description: "Search documents in one collection. Use query for a loose text search where possible.",
|
|
253
|
-
inputSchema: z.object({
|
|
254
|
-
collection: collectionSlugSchema,
|
|
255
|
-
limit: z.number().int().min(1).max(10).default(5),
|
|
256
|
-
query: z.string().optional()
|
|
257
|
-
}),
|
|
258
|
-
execute: async ({ collection, limit, query })=>{
|
|
259
|
-
const collectionConfig = allowedCollections.find((item)=>item.slug === collection);
|
|
260
|
-
const searchableFields = collectionConfig?.fields.filter((field)=>"name" in field && [
|
|
261
|
-
"email",
|
|
262
|
-
"text",
|
|
263
|
-
"textarea"
|
|
264
|
-
].includes(field.type)).map((field)=>"name" in field ? field.name : null).filter(Boolean) || [];
|
|
265
|
-
const where = query && searchableFields.length > 0 ? {
|
|
266
|
-
or: searchableFields.map((field)=>({
|
|
267
|
-
[field]: {
|
|
268
|
-
contains: query
|
|
269
|
-
}
|
|
270
|
-
}))
|
|
271
|
-
} : undefined;
|
|
272
|
-
return req.payload.find({
|
|
273
|
-
collection: collection,
|
|
274
|
-
depth: 1,
|
|
275
|
-
limit,
|
|
276
|
-
overrideAccess: false,
|
|
277
|
-
req,
|
|
278
|
-
where
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
const result = await generateText({
|
|
284
|
-
maxOutputTokens: 700,
|
|
285
|
-
model: getModel({
|
|
286
|
-
apiKey: providerConfig.apiKey,
|
|
287
|
-
model: providerConfig.modelID,
|
|
288
|
-
provider
|
|
289
|
-
}),
|
|
290
|
-
prompt: buildPromptWithMentionContext({
|
|
291
|
-
mentionContext,
|
|
292
|
-
prompt
|
|
293
|
-
}),
|
|
294
|
-
stopWhen: stepCountIs(6),
|
|
295
|
-
system: [
|
|
296
|
-
"You are a CMS assistant inside Payload CMS.",
|
|
297
|
-
"Use tools to inspect CMS schema and content before answering content questions.",
|
|
298
|
-
"When mention context is provided, use it as the selected CMS scope and prefer it over guessing collection names.",
|
|
299
|
-
"Never claim that a write has been applied unless the user confirms an action proposal in the UI.",
|
|
300
|
-
"For create, update, and delete requests, call the proposal tools instead of directly changing data.",
|
|
301
|
-
"Keep the visible response under 80 words. Put concrete changes in proposal tool calls instead of long prose."
|
|
302
|
-
].join("\n"),
|
|
303
|
-
tools
|
|
304
|
-
});
|
|
305
|
-
return Response.json({
|
|
306
|
-
proposals,
|
|
307
|
-
text: result.text
|
|
308
|
-
});
|
|
309
|
-
} catch (err) {
|
|
310
|
-
req.payload.logger.error({
|
|
311
|
-
debug,
|
|
312
|
-
err,
|
|
313
|
-
msg: "AI chat request failed"
|
|
314
|
-
});
|
|
315
|
-
return Response.json({
|
|
316
|
-
error: "AI request failed."
|
|
317
|
-
}, {
|
|
318
|
-
status: 500
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { PayloadHandler } from "payload";
|
|
2
|
-
import { type ResolvedAICollectionPermissionMap } from "../payload/collectionPermissions.js";
|
|
3
|
-
type AIMentionSuggestionsEndpointOptions = {
|
|
4
|
-
collections?: ResolvedAICollectionPermissionMap;
|
|
5
|
-
};
|
|
6
|
-
export declare const createAIMentionSuggestionsEndpointHandler: (options?: AIMentionSuggestionsEndpointOptions) => PayloadHandler;
|
|
7
|
-
export {};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
|
|
2
|
-
const getDocLabel = (doc, useAsTitle)=>{
|
|
3
|
-
const titleField = useAsTitle && typeof doc[useAsTitle] === "string" ? doc[useAsTitle] : null;
|
|
4
|
-
return (titleField || doc.title || doc.name || doc.email || doc.id || "Untitled").toString();
|
|
5
|
-
};
|
|
6
|
-
export const createAIMentionSuggestionsEndpointHandler = (options = {})=>async (req)=>{
|
|
7
|
-
if (!req.user) return Response.json({
|
|
8
|
-
error: "Unauthorized"
|
|
9
|
-
}, {
|
|
10
|
-
status: 401
|
|
11
|
-
});
|
|
12
|
-
const body = req.json ? await req.json().catch(()=>null) : null;
|
|
13
|
-
const query = body?.query?.trim();
|
|
14
|
-
const collectionSlug = body?.collectionSlug?.trim();
|
|
15
|
-
if (!query && !collectionSlug) return Response.json({
|
|
16
|
-
suggestions: []
|
|
17
|
-
});
|
|
18
|
-
const suggestions = [];
|
|
19
|
-
const collections = req.payload.config.collections.filter((collection)=>{
|
|
20
|
-
if (!isCollectionActionAllowed({
|
|
21
|
-
action: "read",
|
|
22
|
-
permissions: options.collections,
|
|
23
|
-
req,
|
|
24
|
-
slug: collection.slug
|
|
25
|
-
})) return false;
|
|
26
|
-
if (collectionSlug) return collection.slug === collectionSlug;
|
|
27
|
-
return true;
|
|
28
|
-
});
|
|
29
|
-
for (const collection of collections){
|
|
30
|
-
const searchableFields = collection.fields.filter((field)=>"name" in field && [
|
|
31
|
-
"email",
|
|
32
|
-
"text",
|
|
33
|
-
"textarea"
|
|
34
|
-
].includes(field.type)).map((field)=>"name" in field ? field.name : null).filter(Boolean);
|
|
35
|
-
if (query && searchableFields.length === 0) continue;
|
|
36
|
-
const result = await req.payload.find({
|
|
37
|
-
collection: collection.slug,
|
|
38
|
-
depth: 0,
|
|
39
|
-
limit: 3,
|
|
40
|
-
overrideAccess: false,
|
|
41
|
-
req,
|
|
42
|
-
where: query && searchableFields.length > 0 ? {
|
|
43
|
-
or: searchableFields.map((field)=>({
|
|
44
|
-
[field]: {
|
|
45
|
-
contains: query
|
|
46
|
-
}
|
|
47
|
-
}))
|
|
48
|
-
} : undefined
|
|
49
|
-
});
|
|
50
|
-
for (const doc of result.docs){
|
|
51
|
-
suggestions.push({
|
|
52
|
-
collection: collection.slug,
|
|
53
|
-
id: doc.id?.toString(),
|
|
54
|
-
label: getDocLabel(doc, collection.admin?.useAsTitle),
|
|
55
|
-
slug: `${collection.slug}:${doc.id?.toString()}`,
|
|
56
|
-
type: "doc"
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return Response.json({
|
|
61
|
-
suggestions: suggestions.slice(0, 5)
|
|
62
|
-
});
|
|
63
|
-
};
|