@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.
@@ -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__*/ React.createElement("div", {
61
+ return /*#__PURE__*/ _jsx("div", {
61
62
  className: styles.popover,
62
63
  ref: containerRef,
63
- style: style
64
- }, suggestionGroups.map((group)=>{
65
- const groupSuggestions = suggestions.filter((s)=>s.type === group.type);
66
- if (groupSuggestions.length === 0) return null;
67
- return /*#__PURE__*/ React.createElement("div", {
68
- className: styles.group,
69
- key: group.type
70
- }, /*#__PURE__*/ React.createElement("div", {
71
- className: styles.groupLabel
72
- }, group.label), groupSuggestions.map((suggestion)=>/*#__PURE__*/ React.createElement("button", {
73
- className: styles.option,
74
- key: `${suggestion.type}-${suggestion.slug}-${suggestion.parent || ""}-${suggestion.collection || ""}-${suggestion.id || ""}`,
75
- onMouseDown: (e)=>onMouseDown(e, suggestion),
76
- onKeyDown: (e)=>onKeyDown(e, suggestion),
77
- type: "button"
78
- }, /*#__PURE__*/ React.createElement("span", {
79
- className: styles.slug
80
- }, getSuggestionTitle(suggestion)), /*#__PURE__*/ React.createElement("span", {
81
- className: styles.label
82
- }, getSuggestionLabel(suggestion)))));
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-plugin/client#AIApiKeyField"
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(`payload-ai-plugin/client#AIInput`);
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": "0.5.10",
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 {dist,*.tsbuildinfo}",
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
  }