@mvriu5/payload-ai 1.1.1 → 1.3.2

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.
Files changed (55) hide show
  1. package/README.md +42 -0
  2. package/dist/components/Icons.d.ts +0 -8
  3. package/dist/components/Icons.js +4 -216
  4. package/dist/components/{ActionToast.d.ts → action-toast/ActionToast.d.ts} +2 -2
  5. package/dist/components/{ActionToast.js → action-toast/ActionToast.js} +27 -35
  6. package/dist/components/{ActionToast.module.css → action-toast/ActionToast.module.css} +0 -79
  7. package/dist/components/ai-input/AIInput.js +433 -0
  8. package/dist/components/{AIInput.module.css → ai-input/AIInput.module.css} +119 -42
  9. package/dist/components/ai-input/badge.d.ts +14 -0
  10. package/dist/components/ai-input/badge.js +85 -0
  11. package/dist/components/{AuditLogList.d.ts → audit-log-list/AuditLogList.d.ts} +4 -8
  12. package/dist/components/{AuditLogList.js → audit-log-list/AuditLogList.js} +49 -21
  13. package/dist/components/{AuditLogList.module.css → audit-log-list/AuditLogList.module.css} +11 -65
  14. package/dist/components/dashboard/Dashboard.d.ts +2 -0
  15. package/dist/components/dashboard/Dashboard.js +15 -0
  16. package/dist/components/dashboard/Dashboard.module.css +13 -0
  17. package/dist/components/{DiffDialog.d.ts → diff-dialog/DiffDialog.d.ts} +2 -2
  18. package/dist/components/{DiffDialog.js → diff-dialog/DiffDialog.js} +200 -189
  19. package/dist/components/{DiffDialog.module.css → diff-dialog/DiffDialog.module.css} +17 -35
  20. package/dist/components/hooks/useAIChatStream.d.ts +39 -0
  21. package/dist/components/hooks/useAIChatStream.js +217 -0
  22. package/dist/components/hooks/useAISettings.js +26 -25
  23. package/dist/components/hooks/useAuditLog.d.ts +11 -0
  24. package/dist/components/hooks/useAuditLog.js +41 -0
  25. package/dist/components/hooks/useDocumentMentionSuggestions.d.ts +1 -1
  26. package/dist/components/hooks/useDocumentMentionSuggestions.js +31 -27
  27. package/dist/components/hooks/useMentions.d.ts +58 -0
  28. package/dist/components/hooks/useMentions.js +276 -0
  29. package/dist/components/hooks/usePluginConfig.d.ts +52 -0
  30. package/dist/components/hooks/usePluginConfig.js +20 -0
  31. package/dist/components/{MentionPopover.d.ts → mention-popover/MentionPopover.d.ts} +2 -4
  32. package/dist/components/{MentionPopover.js → mention-popover/MentionPopover.js} +1 -8
  33. package/dist/components/{MentionPopover.module.css → mention-popover/MentionPopover.module.css} +1 -1
  34. package/dist/exports/client.d.ts +2 -2
  35. package/dist/exports/client.js +2 -2
  36. package/dist/handlers/applyActionHandler.js +341 -112
  37. package/dist/handlers/chatHandler.js +940 -84
  38. package/dist/handlers/mediaUploadHandler.d.ts +7 -0
  39. package/dist/handlers/mediaUploadHandler.js +99 -0
  40. package/dist/handlers/mentionSuggestionHandler.js +16 -14
  41. package/dist/handlers/proposalDiffHandler.js +117 -50
  42. package/dist/index.d.ts +6 -0
  43. package/dist/index.js +39 -7
  44. package/dist/payload/collectionPermissions.js +3 -1
  45. package/dist/payload/logging.d.ts +9 -0
  46. package/dist/payload/logging.js +14 -0
  47. package/dist/payload/normalizeData.d.ts +16 -12
  48. package/dist/payload/normalizeData.js +47 -14
  49. package/dist/payload/proposalData.d.ts +31 -0
  50. package/dist/payload/proposalData.js +578 -0
  51. package/dist/payload/schemaContext.d.ts +3 -7
  52. package/dist/payload/schemaContext.js +147 -66
  53. package/package.json +6 -6
  54. package/dist/components/AIInput.js +0 -747
  55. /package/dist/components/{AIInput.d.ts → ai-input/AIInput.d.ts} +0 -0
@@ -0,0 +1,276 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { getSerializableLabel, isInternalCollection } from "../../payload/shared.js";
4
+ import { createBadgePrefix, getTextNodeAtOffset, replaceTextRangeWithBadge } from "../ai-input/badge.js";
5
+ import { useDocumentMentionSuggestions } from "../hooks/useDocumentMentionSuggestions.js";
6
+ const isMentionBoundary = (character)=>{
7
+ return character === undefined || /\s/.test(character);
8
+ };
9
+ const collectBlockOptions = ({ fields, parent })=>{
10
+ const options = [];
11
+ for (const field of fields){
12
+ if (!field.fields) continue;
13
+ options.push(...collectBlockOptions({
14
+ fields: field.fields,
15
+ parent
16
+ }));
17
+ }
18
+ return options;
19
+ };
20
+ const getActiveMentionRange = (valueBeforeCaret)=>{
21
+ const caretPosition = valueBeforeCaret.length;
22
+ for(let index = valueBeforeCaret.length - 1; index >= 0; index -= 1){
23
+ const character = valueBeforeCaret[index];
24
+ if (character === "@") {
25
+ const previousCharacter = valueBeforeCaret[index - 1];
26
+ const query = valueBeforeCaret.slice(index + 1);
27
+ if (!isMentionBoundary(previousCharacter)) return null;
28
+ if (!/^[\w-]*$/.test(query)) return null;
29
+ return {
30
+ query,
31
+ range: {
32
+ end: caretPosition,
33
+ start: index
34
+ }
35
+ };
36
+ }
37
+ if (isMentionBoundary(character)) break;
38
+ }
39
+ return null;
40
+ };
41
+ export const getTextBeforeCaret = (element)=>{
42
+ const selection = window.getSelection();
43
+ if (!selection || selection.rangeCount === 0) return "";
44
+ const range = selection.getRangeAt(0);
45
+ const clonedRange = range.cloneRange();
46
+ clonedRange.selectNodeContents(element);
47
+ clonedRange.setEnd(range.endContainer, range.endOffset);
48
+ return clonedRange.toString();
49
+ };
50
+ export const useMentions = ({ apiRoute, config, defaultLocale, editorRef, isCollectionMentionEnabled, locales, setPrompt, styles })=>{
51
+ const mentionsRef = useRef([]);
52
+ const [mentionQuery, setMentionQuery] = useState("");
53
+ const [mentionRange, setMentionRange] = useState(null);
54
+ const [mentionPopoverPosition, setMentionPopoverPosition] = useState(null);
55
+ const collections = useMemo(()=>config.collections.flatMap((collection)=>{
56
+ if (isInternalCollection(collection.slug) || !isCollectionMentionEnabled(collection.slug)) return [];
57
+ return [
58
+ {
59
+ label: getSerializableLabel(collection.labels?.singular, collection.slug),
60
+ slug: collection.slug,
61
+ type: "collection"
62
+ }
63
+ ];
64
+ }), [
65
+ config.collections,
66
+ isCollectionMentionEnabled
67
+ ]);
68
+ const globals = useMemo(()=>config.globals?.map((global)=>({
69
+ label: getSerializableLabel(global.label, global.slug),
70
+ slug: global.slug,
71
+ type: "global"
72
+ })) || [], [
73
+ config.globals
74
+ ]);
75
+ const blocks = useMemo(()=>[
76
+ ...config.collections.flatMap((collection)=>{
77
+ if (!isCollectionMentionEnabled(collection.slug)) return [];
78
+ return collectBlockOptions({
79
+ fields: collection.fields,
80
+ parent: collection.slug
81
+ });
82
+ }),
83
+ ...config.globals?.flatMap((global)=>collectBlockOptions({
84
+ fields: global.fields,
85
+ parent: global.slug
86
+ })) || []
87
+ ], [
88
+ config.collections,
89
+ config.globals,
90
+ isCollectionMentionEnabled
91
+ ]);
92
+ const localeOptions = useMemo(()=>locales.flatMap((locale)=>{
93
+ if (typeof locale === "string") {
94
+ return [
95
+ {
96
+ isDefault: locale === defaultLocale,
97
+ label: locale,
98
+ slug: locale,
99
+ type: "locale"
100
+ }
101
+ ];
102
+ }
103
+ if (!locale || typeof locale !== "object") return [];
104
+ const slug = typeof locale.code === "string" ? locale.code : null;
105
+ if (!slug) return [];
106
+ return [
107
+ {
108
+ isDefault: slug === defaultLocale,
109
+ label: getSerializableLabel(locale.label, slug),
110
+ slug,
111
+ type: "locale"
112
+ }
113
+ ];
114
+ }), [
115
+ defaultLocale,
116
+ locales
117
+ ]);
118
+ const mentionOptions = useMemo(()=>[
119
+ ...collections,
120
+ ...globals,
121
+ ...blocks,
122
+ ...localeOptions
123
+ ], [
124
+ blocks,
125
+ collections,
126
+ globals,
127
+ localeOptions
128
+ ]);
129
+ const normalizedMentionQuery = mentionQuery.toLowerCase();
130
+ const filteredCollections = useMemo(()=>collections.filter((collection)=>collection.slug.toLowerCase().includes(normalizedMentionQuery) || collection.label.toLowerCase().includes(normalizedMentionQuery)), [
131
+ collections,
132
+ normalizedMentionQuery
133
+ ]);
134
+ const filteredMentionOptions = useMemo(()=>mentionOptions.filter((option)=>option.slug.toLowerCase().includes(normalizedMentionQuery) || option.label.toLowerCase().includes(normalizedMentionQuery)), [
135
+ mentionOptions,
136
+ normalizedMentionQuery
137
+ ]);
138
+ const documentSuggestionCollection = filteredCollections.length === 1 ? filteredCollections[0]?.slug : null;
139
+ const { documentSuggestions, resetDocumentSuggestions } = useDocumentMentionSuggestions({
140
+ apiRoute,
141
+ documentSuggestionCollection,
142
+ mentionQuery,
143
+ mentionRange
144
+ });
145
+ const mentionSuggestions = useMemo(()=>[
146
+ ...filteredMentionOptions,
147
+ ...documentSuggestions
148
+ ], [
149
+ documentSuggestions,
150
+ filteredMentionOptions
151
+ ]);
152
+ const updateMentionPopoverPosition = useCallback((range)=>{
153
+ const editor = editorRef.current;
154
+ if (!editor || !range) {
155
+ setMentionPopoverPosition(null);
156
+ return;
157
+ }
158
+ const startPosition = getTextNodeAtOffset(editor, range.start);
159
+ const anchorRange = document.createRange();
160
+ anchorRange.setStart(startPosition.node, startPosition.offset);
161
+ anchorRange.collapse(true);
162
+ const marker = document.createElement("span");
163
+ marker.textContent = "\u200b";
164
+ anchorRange.insertNode(marker);
165
+ const rect = anchorRange.getBoundingClientRect();
166
+ marker.remove();
167
+ if (rect.left === 0 && rect.top === 0) {
168
+ setMentionPopoverPosition(null);
169
+ return;
170
+ }
171
+ setMentionPopoverPosition({
172
+ left: rect.left,
173
+ top: rect.bottom + 4
174
+ });
175
+ }, [
176
+ editorRef
177
+ ]);
178
+ const resetMentionState = useCallback(()=>{
179
+ setMentionQuery("");
180
+ setMentionRange(null);
181
+ setMentionPopoverPosition(null);
182
+ }, []);
183
+ const updateMentionState = useCallback((valueBeforeCaret)=>{
184
+ const activeMention = getActiveMentionRange(valueBeforeCaret);
185
+ if (!activeMention) {
186
+ resetMentionState();
187
+ return;
188
+ }
189
+ setMentionQuery(activeMention.query);
190
+ setMentionRange(activeMention.range);
191
+ updateMentionPopoverPosition(activeMention.range);
192
+ }, [
193
+ resetMentionState,
194
+ updateMentionPopoverPosition
195
+ ]);
196
+ useEffect(()=>{
197
+ if (!mentionRange) return;
198
+ const updatePosition = ()=>{
199
+ updateMentionPopoverPosition(mentionRange);
200
+ };
201
+ window.addEventListener("resize", updatePosition);
202
+ window.addEventListener("scroll", updatePosition, true);
203
+ return ()=>{
204
+ window.removeEventListener("resize", updatePosition);
205
+ window.removeEventListener("scroll", updatePosition, true);
206
+ };
207
+ }, [
208
+ mentionRange,
209
+ updateMentionPopoverPosition
210
+ ]);
211
+ const insertMention = useCallback((suggestion)=>{
212
+ const editor = editorRef.current;
213
+ if (!mentionRange || !editor) return;
214
+ const currentValue = editor.textContent || "";
215
+ const beforeMention = currentValue.slice(0, mentionRange.start);
216
+ const afterMention = currentValue.slice(mentionRange.end);
217
+ const badgeType = suggestion.type === "doc" ? "document" : suggestion.type;
218
+ const badgePrefix = `${badgeType}:`;
219
+ const badgeText = `${badgePrefix} ${suggestion.label}`;
220
+ const badge = document.createElement("span");
221
+ badge.className = [
222
+ styles.badge,
223
+ styles[suggestion.type],
224
+ styles.inlineBadge
225
+ ].join(" ");
226
+ badge.contentEditable = "false";
227
+ badge.append(createBadgePrefix(suggestion, styles), Object.assign(document.createElement("span"), {
228
+ className: styles.name,
229
+ textContent: suggestion.label
230
+ }));
231
+ const nextPrompt = `${beforeMention}${badgeText} ${afterMention}`;
232
+ replaceTextRangeWithBadge({
233
+ badge,
234
+ editor,
235
+ end: mentionRange.end,
236
+ start: mentionRange.start
237
+ });
238
+ editor.focus();
239
+ setPrompt(nextPrompt);
240
+ const mentionExists = mentionsRef.current.some((mention)=>mention.type === suggestion.type && mention.slug === suggestion.slug && mention.parent === suggestion.parent && mention.collection === suggestion.collection && mention.id === suggestion.id);
241
+ if (!mentionExists) {
242
+ mentionsRef.current = [
243
+ ...mentionsRef.current,
244
+ suggestion
245
+ ];
246
+ }
247
+ resetMentionState();
248
+ resetDocumentSuggestions();
249
+ }, [
250
+ editorRef,
251
+ mentionRange,
252
+ resetDocumentSuggestions,
253
+ resetMentionState,
254
+ setPrompt,
255
+ styles
256
+ ]);
257
+ const clearMentions = useCallback(()=>{
258
+ mentionsRef.current = [];
259
+ resetMentionState();
260
+ resetDocumentSuggestions();
261
+ }, [
262
+ resetDocumentSuggestions,
263
+ resetMentionState
264
+ ]);
265
+ return {
266
+ clearMentions,
267
+ getTextBeforeCaret,
268
+ insertMention,
269
+ mentionPopoverPosition,
270
+ mentionQuery,
271
+ mentionRange,
272
+ mentionSuggestions,
273
+ mentionsRef,
274
+ updateMentionState
275
+ };
276
+ };
@@ -0,0 +1,52 @@
1
+ type MediaConfig = {
2
+ acceptedMimeTypes?: string[];
3
+ collectionSlug: string;
4
+ enabled: boolean;
5
+ maxFileSize?: number;
6
+ };
7
+ type LocaleConfig = string | {
8
+ code?: string;
9
+ label?: unknown;
10
+ };
11
+ type LocalizationConfig = false | {
12
+ defaultLocale?: string;
13
+ locales?: LocaleConfig[];
14
+ };
15
+ export declare const usePluginConfig: (config: {
16
+ admin?: {
17
+ custom?: unknown;
18
+ };
19
+ localization?: LocalizationConfig;
20
+ }) => {
21
+ aiModelConfig: {
22
+ defaults: Record<"claude" | "google" | "mistral" | "openai" | "openrouter", string>;
23
+ providers: {
24
+ claude: {
25
+ label: string;
26
+ value: string;
27
+ }[];
28
+ google: {
29
+ label: string;
30
+ value: string;
31
+ }[];
32
+ mistral: {
33
+ label: string;
34
+ value: string;
35
+ }[];
36
+ openai: {
37
+ label: string;
38
+ value: string;
39
+ }[];
40
+ openrouter: {
41
+ label: string;
42
+ value: string;
43
+ }[];
44
+ };
45
+ };
46
+ defaultLocale: string | undefined;
47
+ enabledCollectionSlugSet: Set<string> | null;
48
+ isCollectionMentionEnabled: (slug: string) => boolean;
49
+ locales: LocaleConfig[];
50
+ media: MediaConfig | undefined;
51
+ };
52
+ export {};
@@ -0,0 +1,20 @@
1
+ import { useMemo } from "react";
2
+ import { getResolvedAIModelConfig } from "../../ai/providerOptions.js";
3
+ export const usePluginConfig = (config)=>{
4
+ const pluginConfig = config.admin?.custom?.payloadAiPlugin;
5
+ const aiModelConfig = useMemo(()=>getResolvedAIModelConfig(pluginConfig?.models), [
6
+ pluginConfig?.models
7
+ ]);
8
+ const enabledCollectionSlugSet = useMemo(()=>pluginConfig?.collectionSlugs ? new Set(pluginConfig.collectionSlugs) : null, [
9
+ pluginConfig?.collectionSlugs
10
+ ]);
11
+ const localization = config.localization && typeof config.localization === "object" ? config.localization : null;
12
+ return {
13
+ aiModelConfig,
14
+ defaultLocale: localization?.defaultLocale,
15
+ enabledCollectionSlugSet,
16
+ isCollectionMentionEnabled: (slug)=>!enabledCollectionSlugSet || enabledCollectionSlugSet.has(slug),
17
+ locales: localization?.locales ?? [],
18
+ media: pluginConfig?.media
19
+ };
20
+ };
@@ -1,5 +1,4 @@
1
1
  import type { CSSProperties } from "react";
2
- import type { RefObject } from "react";
3
2
  export type MentionOption = {
4
3
  collection?: string;
5
4
  id?: string;
@@ -7,13 +6,12 @@ export type MentionOption = {
7
6
  label: string;
8
7
  parent?: string;
9
8
  slug: string;
10
- type: "block" | "collection" | "doc" | "global" | "locale";
9
+ type: "collection" | "doc" | "global" | "locale";
11
10
  };
12
11
  type MentionPopoverProps = {
13
- containerRef?: RefObject<HTMLDivElement | null>;
14
12
  suggestions: MentionOption[];
15
13
  onSelect: (suggestion: MentionOption) => void;
16
14
  style?: CSSProperties;
17
15
  };
18
- export declare const MentionPopover: ({ containerRef, onSelect, style, suggestions }: MentionPopoverProps) => import("react").JSX.Element | null;
16
+ export declare const MentionPopover: ({ onSelect, style, suggestions }: MentionPopoverProps) => import("react").JSX.Element | null;
19
17
  export {};
@@ -13,10 +13,6 @@ const suggestionGroups = [
13
13
  label: "Globals",
14
14
  type: "global"
15
15
  },
16
- {
17
- label: "Blocks",
18
- type: "block"
19
- },
20
16
  {
21
17
  label: "Locales",
22
18
  type: "locale"
@@ -25,18 +21,16 @@ const suggestionGroups = [
25
21
  const getSuggestionTitle = (suggestion)=>{
26
22
  if (suggestion.type === "collection") return `@${suggestion.slug}`;
27
23
  if (suggestion.type === "global") return `@${suggestion.slug}`;
28
- if (suggestion.type === "block") return `@${suggestion.slug}`;
29
24
  if (suggestion.type === "locale") return `@${suggestion.slug}`;
30
25
  return suggestion.label;
31
26
  };
32
27
  const getSuggestionLabel = (suggestion)=>{
33
28
  if (suggestion.type === "collection") return suggestion.label;
34
29
  if (suggestion.type === "global") return "global";
35
- if (suggestion.type === "block") return `${suggestion.parent} block`;
36
30
  if (suggestion.type === "locale") return suggestion.isDefault ? "default locale" : "locale";
37
31
  return `${suggestion.collection} item`;
38
32
  };
39
- export const MentionPopover = ({ containerRef, onSelect, style, suggestions })=>{
33
+ export const MentionPopover = ({ onSelect, style, suggestions })=>{
40
34
  if (suggestions.length === 0) return null;
41
35
  const onKeyDown = (event, suggestion)=>{
42
36
  if (event.key === "Enter" || event.key === " ") {
@@ -60,7 +54,6 @@ export const MentionPopover = ({ containerRef, onSelect, style, suggestions })=>
60
54
  };
61
55
  return /*#__PURE__*/ _jsx("div", {
62
56
  className: styles.popover,
63
- ref: containerRef,
64
57
  style: style,
65
58
  children: suggestionGroups.map((group)=>{
66
59
  const groupSuggestions = suggestions.filter((s)=>s.type === group.type);
@@ -8,7 +8,7 @@
8
8
  min-width: 260px;
9
9
  overflow: auto;
10
10
  padding: 6px;
11
- position: absolute;
11
+ position: fixed;
12
12
  z-index: 10;
13
13
  }
14
14
 
@@ -1,4 +1,4 @@
1
- import AIInput from "../components/AIInput.js";
1
+ import Dashboard from "../components/dashboard/Dashboard.js";
2
2
  import APIKeyField from "../components/APIKeyField.js";
3
- export { AIInput };
3
+ export { Dashboard };
4
4
  export { APIKeyField as AIApiKeyField };
@@ -1,4 +1,4 @@
1
- import AIInput from "../components/AIInput.js";
1
+ import Dashboard from "../components/dashboard/Dashboard.js";
2
2
  import APIKeyField from "../components/APIKeyField.js";
3
- export { AIInput };
3
+ export { Dashboard };
4
4
  export { APIKeyField as AIApiKeyField };