@mvriu5/payload-ai 0.5.10 → 1.1.1
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/dist/components/AIInput.js +166 -109
- package/dist/components/APIKeyField.js +41 -24
- package/dist/components/ActionToast.js +200 -101
- package/dist/components/AuditLogList.js +90 -53
- package/dist/components/DiffDialog.js +222 -121
- package/dist/components/Icons.js +390 -274
- package/dist/components/MentionPopover.js +33 -22
- package/dist/handlers/chatHandler.js +89 -2
- package/dist/index.js +2 -2
- package/package.json +40 -9
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
2
|
import styles from "./MentionPopover.module.css";
|
|
2
3
|
const suggestionGroups = [
|
|
3
4
|
{
|
|
@@ -57,28 +58,38 @@ export const MentionPopover = ({ containerRef, onSelect, style, suggestions })=>
|
|
|
57
58
|
event.preventDefault();
|
|
58
59
|
onSelect(suggestion);
|
|
59
60
|
};
|
|
60
|
-
return /*#__PURE__*/
|
|
61
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
61
62
|
className: styles.popover,
|
|
62
63
|
ref: containerRef,
|
|
63
|
-
style: style
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
64
|
+
style: style,
|
|
65
|
+
children: suggestionGroups.map((group)=>{
|
|
66
|
+
const groupSuggestions = suggestions.filter((s)=>s.type === group.type);
|
|
67
|
+
if (groupSuggestions.length === 0) return null;
|
|
68
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
69
|
+
className: styles.group,
|
|
70
|
+
children: [
|
|
71
|
+
/*#__PURE__*/ _jsx("div", {
|
|
72
|
+
className: styles.groupLabel,
|
|
73
|
+
children: group.label
|
|
74
|
+
}),
|
|
75
|
+
groupSuggestions.map((suggestion)=>/*#__PURE__*/ _jsxs("button", {
|
|
76
|
+
className: styles.option,
|
|
77
|
+
onMouseDown: (e)=>onMouseDown(e, suggestion),
|
|
78
|
+
onKeyDown: (e)=>onKeyDown(e, suggestion),
|
|
79
|
+
type: "button",
|
|
80
|
+
children: [
|
|
81
|
+
/*#__PURE__*/ _jsx("span", {
|
|
82
|
+
className: styles.slug,
|
|
83
|
+
children: getSuggestionTitle(suggestion)
|
|
84
|
+
}),
|
|
85
|
+
/*#__PURE__*/ _jsx("span", {
|
|
86
|
+
className: styles.label,
|
|
87
|
+
children: getSuggestionLabel(suggestion)
|
|
88
|
+
})
|
|
89
|
+
]
|
|
90
|
+
}, `${suggestion.type}-${suggestion.slug}-${suggestion.parent || ""}-${suggestion.collection || ""}-${suggestion.id || ""}`))
|
|
91
|
+
]
|
|
92
|
+
}, group.type);
|
|
93
|
+
})
|
|
94
|
+
});
|
|
84
95
|
};
|
|
@@ -7,6 +7,87 @@ import { containsSensitiveData } from "../ai/sensitiveData.js";
|
|
|
7
7
|
import { isCollectionActionAllowed } from "../payload/collectionPermissions.js";
|
|
8
8
|
import { buildPromptWithMentionContext, collectBlocks, describeCollectionLikeConfig, describeCollectionLikeSummary, getAllowedCollectionSlugs, getMentionContext } from "../payload/schemaContext.js";
|
|
9
9
|
import { getOptionValue, getSafeProposalLabel, hasLocalizedData, hasValueAtPath, setValueAtPath } from "../payload/shared.js";
|
|
10
|
+
const e2eModeEnabled = ()=>process.env.PAYLOAD_AI_E2E_MODE === "true";
|
|
11
|
+
const createSSEEventStream = (events)=>{
|
|
12
|
+
const encoder = new TextEncoder();
|
|
13
|
+
return new ReadableStream({
|
|
14
|
+
start (controller) {
|
|
15
|
+
for (const { data, event } of events){
|
|
16
|
+
controller.enqueue(encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`));
|
|
17
|
+
}
|
|
18
|
+
controller.close();
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
const createE2EChatResponse = ({ prompt, selectedLocales })=>{
|
|
23
|
+
const normalizedPrompt = prompt.toLowerCase();
|
|
24
|
+
const wantsCreatePost = normalizedPrompt.includes("post") && (normalizedPrompt.includes("create") || normalizedPrompt.includes("erstell"));
|
|
25
|
+
const mentionsMars = normalizedPrompt.includes("mars");
|
|
26
|
+
const multipleLocales = selectedLocales.length > 1;
|
|
27
|
+
const activeLocale = selectedLocales.at(-1);
|
|
28
|
+
const proposalLabel = multipleLocales ? normalizedPrompt.includes("locale review") ? "Create localized locale review draft post about Mars" : "Create localized draft post about Mars" : normalizedPrompt.includes("apply flow") ? "Create apply flow draft post about Mars" : normalizedPrompt.includes("proposal review") ? "Create proposal review draft post about Mars" : "Create draft post about Mars";
|
|
29
|
+
const proposal = wantsCreatePost && mentionsMars ? signAIActionProposal(multipleLocales ? {
|
|
30
|
+
action: "create",
|
|
31
|
+
collection: "posts",
|
|
32
|
+
label: proposalLabel,
|
|
33
|
+
localizedData: Object.fromEntries(selectedLocales.map((locale)=>[
|
|
34
|
+
locale,
|
|
35
|
+
{
|
|
36
|
+
content: locale === "de" ? "Mars ist der vierte Planet von der Sonne." : "Mars is the fourth planet from the Sun.",
|
|
37
|
+
excerpt: locale === "de" ? "Ein kurzer Entwurf ueber Mars." : "A short draft about Mars.",
|
|
38
|
+
title: locale === "de" ? "Mars im Ueberblick" : "Mars Overview"
|
|
39
|
+
}
|
|
40
|
+
])),
|
|
41
|
+
...activeLocale ? {
|
|
42
|
+
locale: activeLocale
|
|
43
|
+
} : {}
|
|
44
|
+
} : {
|
|
45
|
+
action: "create",
|
|
46
|
+
collection: "posts",
|
|
47
|
+
data: {
|
|
48
|
+
content: "Mars is the fourth planet from the Sun.",
|
|
49
|
+
excerpt: "A short draft about Mars.",
|
|
50
|
+
status: "draft",
|
|
51
|
+
title: "Mars Overview"
|
|
52
|
+
},
|
|
53
|
+
label: proposalLabel,
|
|
54
|
+
...activeLocale ? {
|
|
55
|
+
locale: activeLocale
|
|
56
|
+
} : {}
|
|
57
|
+
}) : null;
|
|
58
|
+
const responseText = proposal ? "Prepared one draft post proposal." : "No content change was proposed.";
|
|
59
|
+
return new Response(createSSEEventStream([
|
|
60
|
+
{
|
|
61
|
+
data: {
|
|
62
|
+
delta: responseText
|
|
63
|
+
},
|
|
64
|
+
event: "text"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
data: {
|
|
68
|
+
proposals: proposal ? [
|
|
69
|
+
proposal
|
|
70
|
+
] : [],
|
|
71
|
+
usage: {
|
|
72
|
+
inputTokens: 42,
|
|
73
|
+
outputTokens: 27,
|
|
74
|
+
totalTokens: 69
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
event: "proposals"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
data: {},
|
|
81
|
+
event: "done"
|
|
82
|
+
}
|
|
83
|
+
]), {
|
|
84
|
+
headers: {
|
|
85
|
+
"Cache-Control": "no-cache, no-transform",
|
|
86
|
+
Connection: "keep-alive",
|
|
87
|
+
"Content-Type": "text/event-stream; charset=utf-8"
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
};
|
|
10
91
|
const getRequiredFieldInfos = (fields, titleFieldName, path = "")=>{
|
|
11
92
|
return fields.flatMap((field)=>{
|
|
12
93
|
const fieldPath = field.name ? path ? `${path}.${field.name}` : field.name : path;
|
|
@@ -147,6 +228,14 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
147
228
|
}, {
|
|
148
229
|
status: 400
|
|
149
230
|
});
|
|
231
|
+
const selectedLocales = (body?.mentions?.filter((mention)=>mention.type === "locale" && mention.slug).map((mention)=>mention.slug) || []).filter((locale, index, array)=>array.indexOf(locale) === index);
|
|
232
|
+
const activeLocale = selectedLocales.at(-1);
|
|
233
|
+
if (e2eModeEnabled()) {
|
|
234
|
+
return createE2EChatResponse({
|
|
235
|
+
prompt,
|
|
236
|
+
selectedLocales
|
|
237
|
+
});
|
|
238
|
+
}
|
|
150
239
|
const user = req.user;
|
|
151
240
|
const requestedProvider = body?.provider || user.aiProvider || "openai";
|
|
152
241
|
if (!isAIProvider(requestedProvider)) return Response.json({
|
|
@@ -229,8 +318,6 @@ export const createChatHandler = (options = {})=>async (req)=>{
|
|
|
229
318
|
mentions: body?.mentions,
|
|
230
319
|
req
|
|
231
320
|
});
|
|
232
|
-
const selectedLocales = (body?.mentions?.filter((mention)=>mention.type === "locale" && mention.slug).map((mention)=>mention.slug) || []).filter((locale, index, array)=>array.indexOf(locale) === index);
|
|
233
|
-
const activeLocale = selectedLocales.at(-1);
|
|
234
321
|
const mentionedCollectionSlugs = (body?.mentions?.flatMap((mention)=>{
|
|
235
322
|
if (mention.type === "collection" && mention.slug) return [
|
|
236
323
|
mention.slug
|
package/dist/index.js
CHANGED
|
@@ -143,7 +143,7 @@ const addAccountFields = ({ allowUserApiKeys, config })=>{
|
|
|
143
143
|
type: "text",
|
|
144
144
|
admin: {
|
|
145
145
|
components: {
|
|
146
|
-
Field: "payload-ai
|
|
146
|
+
Field: "@mvriu5/payload-ai/client#AIApiKeyField"
|
|
147
147
|
},
|
|
148
148
|
description: "Optional. If empty, the plugin uses the provider API key from environment variables."
|
|
149
149
|
},
|
|
@@ -180,7 +180,7 @@ export const payloadAiPlugin = (pluginOptions)=>(config)=>{
|
|
|
180
180
|
};
|
|
181
181
|
if (!config.admin.components) config.admin.components = {};
|
|
182
182
|
if (!config.admin.components.beforeDashboard) config.admin.components.beforeDashboard = [];
|
|
183
|
-
config.admin.components.beforeDashboard.push(
|
|
183
|
+
config.admin.components.beforeDashboard.push(`@mvriu5/payload-ai/client#AIInput`);
|
|
184
184
|
config.endpoints.push({
|
|
185
185
|
handler: createChatHandler({
|
|
186
186
|
allowUserApiKeys,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mvriu5/payload-ai",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "AI assistant plugin for Payload CMS with provider selection, CMS mentions, and signed action proposals.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"payload",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
|
46
46
|
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
47
47
|
"build:types": "tsc --outDir dist --rootDir ./src",
|
|
48
|
-
"clean": "rimraf
|
|
48
|
+
"clean": "rimraf dist tsconfig.tsbuildinfo",
|
|
49
49
|
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
|
50
50
|
"dev": "next dev dev --turbo",
|
|
51
51
|
"dev:generate-importmap": "pnpm dev:payload generate:importmap",
|
|
@@ -55,9 +55,23 @@
|
|
|
55
55
|
"generate:types": "pnpm dev:generate-types",
|
|
56
56
|
"lint": "eslint",
|
|
57
57
|
"lint:fix": "eslint ./src --fix",
|
|
58
|
-
"prepublishOnly": "pnpm clean && pnpm build"
|
|
58
|
+
"prepublishOnly": "pnpm clean && pnpm build",
|
|
59
|
+
"test": "vitest run tests/components tests/handlers tests/unit",
|
|
60
|
+
"test:all": "pnpm test && pnpm test:e2e",
|
|
61
|
+
"test:components": "vitest run tests/components",
|
|
62
|
+
"test:e2e": "playwright test",
|
|
63
|
+
"test:e2e:headed": "playwright test --headed",
|
|
64
|
+
"test:handlers": "vitest run tests/handlers",
|
|
65
|
+
"test:unit": "vitest run tests/unit",
|
|
66
|
+
"setup:hooks": "git config core.hooksPath .githooks"
|
|
59
67
|
},
|
|
60
68
|
"devDependencies": {
|
|
69
|
+
"@ai-sdk/anthropic": "^3.0.81",
|
|
70
|
+
"@ai-sdk/google": "^3.0.80",
|
|
71
|
+
"@ai-sdk/mistral": "^3.0.37",
|
|
72
|
+
"@ai-sdk/openai": "^3.0.67",
|
|
73
|
+
"@openrouter/ai-sdk-provider": "^2.9.1",
|
|
74
|
+
"@playwright/test": "^1.56.1",
|
|
61
75
|
"@payloadcms/db-postgres": "3.84.1",
|
|
62
76
|
"@payloadcms/next": "3.84.1",
|
|
63
77
|
"@payloadcms/richtext-lexical": "3.84.1",
|
|
@@ -70,6 +84,7 @@
|
|
|
70
84
|
"cross-env": "^7.0.3",
|
|
71
85
|
"eslint": "^10.5.0",
|
|
72
86
|
"graphql": "^16.8.1",
|
|
87
|
+
"jsdom": "^29.1.1",
|
|
73
88
|
"knip": "^6.16.1",
|
|
74
89
|
"next": "16.2.9",
|
|
75
90
|
"payload": "3.84.1",
|
|
@@ -79,12 +94,33 @@
|
|
|
79
94
|
"rimraf": "6.1.3",
|
|
80
95
|
"sharp": "0.35.1",
|
|
81
96
|
"typescript": "5.9.3",
|
|
82
|
-
"vite-tsconfig-paths": "6.1.1",
|
|
83
97
|
"vitest": "4.1.9"
|
|
84
98
|
},
|
|
85
99
|
"peerDependencies": {
|
|
100
|
+
"@ai-sdk/anthropic": "^3.0.81",
|
|
101
|
+
"@ai-sdk/google": "^3.0.80",
|
|
102
|
+
"@ai-sdk/mistral": "^3.0.37",
|
|
103
|
+
"@ai-sdk/openai": "^3.0.67",
|
|
104
|
+
"@openrouter/ai-sdk-provider": "^2.9.1",
|
|
86
105
|
"payload": "^3.84.1"
|
|
87
106
|
},
|
|
107
|
+
"peerDependenciesMeta": {
|
|
108
|
+
"@ai-sdk/anthropic": {
|
|
109
|
+
"optional": true
|
|
110
|
+
},
|
|
111
|
+
"@ai-sdk/google": {
|
|
112
|
+
"optional": true
|
|
113
|
+
},
|
|
114
|
+
"@ai-sdk/mistral": {
|
|
115
|
+
"optional": true
|
|
116
|
+
},
|
|
117
|
+
"@ai-sdk/openai": {
|
|
118
|
+
"optional": true
|
|
119
|
+
},
|
|
120
|
+
"@openrouter/ai-sdk-provider": {
|
|
121
|
+
"optional": true
|
|
122
|
+
}
|
|
123
|
+
},
|
|
88
124
|
"engines": {
|
|
89
125
|
"node": "^18.20.2 || >=20.9.0",
|
|
90
126
|
"pnpm": "^9 || ^10 || ^11"
|
|
@@ -111,11 +147,6 @@
|
|
|
111
147
|
]
|
|
112
148
|
},
|
|
113
149
|
"dependencies": {
|
|
114
|
-
"@ai-sdk/anthropic": "^3.0.81",
|
|
115
|
-
"@ai-sdk/google": "^3.0.80",
|
|
116
|
-
"@ai-sdk/mistral": "^3.0.37",
|
|
117
|
-
"@ai-sdk/openai": "^3.0.67",
|
|
118
|
-
"@openrouter/ai-sdk-provider": "^2.9.1",
|
|
119
150
|
"ai": "^6.0.193",
|
|
120
151
|
"zod": "^4.4.3"
|
|
121
152
|
}
|