@ai-stack/payloadcms 3.2.20-beta → 3.2.22-beta

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 (65) hide show
  1. package/dist/ai/models/anthropic/index.js +9 -15
  2. package/dist/ai/models/anthropic/index.js.map +1 -1
  3. package/dist/ai/models/generateObject.d.ts +11 -0
  4. package/dist/ai/models/generateObject.js +22 -0
  5. package/dist/ai/models/generateObject.js.map +1 -0
  6. package/dist/ai/models/openai/index.js +19 -17
  7. package/dist/ai/models/openai/index.js.map +1 -1
  8. package/dist/collections/Instructions.js +2 -0
  9. package/dist/collections/Instructions.js.map +1 -1
  10. package/dist/endpoints/index.js +55 -6
  11. package/dist/endpoints/index.js.map +1 -1
  12. package/dist/fields/ComposeField/ComposeField.js +5 -3
  13. package/dist/fields/ComposeField/ComposeField.js.map +1 -1
  14. package/dist/fields/ComposeField/ComposeField.jsx +32 -0
  15. package/dist/fields/LexicalEditor/ComposeFeatureComponent.js +1 -1
  16. package/dist/fields/LexicalEditor/ComposeFeatureComponent.js.map +1 -1
  17. package/dist/fields/LexicalEditor/ComposeFeatureComponent.jsx +24 -0
  18. package/dist/fields/LexicalEditor/feature.client.jsx +21 -0
  19. package/dist/fields/PromptEditorField/PromptEditorField.jsx +42 -0
  20. package/dist/fields/SelectField/SelectField.jsx +38 -0
  21. package/dist/providers/FieldProvider/FieldProvider.d.ts +7 -4
  22. package/dist/providers/FieldProvider/FieldProvider.js +7 -6
  23. package/dist/providers/FieldProvider/FieldProvider.js.map +1 -1
  24. package/dist/providers/FieldProvider/FieldProvider.jsx +27 -0
  25. package/dist/providers/FieldProvider/useFieldProps.d.ts +1 -1
  26. package/dist/providers/FieldProvider/useFieldProps.js +2 -2
  27. package/dist/providers/FieldProvider/useFieldProps.js.map +1 -1
  28. package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +45 -0
  29. package/dist/types.d.ts +4 -4
  30. package/dist/types.js.map +1 -1
  31. package/dist/ui/Compose/Compose.js +12 -81
  32. package/dist/ui/Compose/Compose.js.map +1 -1
  33. package/dist/ui/Compose/Compose.jsx +141 -0
  34. package/dist/ui/Compose/UndoRedoActions.jsx +34 -0
  35. package/dist/ui/Compose/compose.module.css +99 -12
  36. package/dist/ui/Compose/hooks/menu/Item.jsx +15 -0
  37. package/dist/ui/Compose/hooks/menu/TranslateMenu.js +1 -2
  38. package/dist/ui/Compose/hooks/menu/TranslateMenu.js.map +1 -1
  39. package/dist/ui/Compose/hooks/menu/TranslateMenu.jsx +58 -0
  40. package/dist/ui/Compose/hooks/menu/items.jsx +10 -0
  41. package/dist/ui/Compose/hooks/menu/useMenu.js +1 -1
  42. package/dist/ui/Compose/hooks/menu/useMenu.js.map +1 -1
  43. package/dist/ui/Compose/hooks/menu/useMenu.jsx +89 -0
  44. package/dist/ui/Compose/hooks/useActiveFieldTracking.d.ts +5 -0
  45. package/dist/ui/Compose/hooks/useActiveFieldTracking.js +225 -0
  46. package/dist/ui/Compose/hooks/useActiveFieldTracking.js.map +1 -0
  47. package/dist/ui/Compose/hooks/useGenerate.js +46 -79
  48. package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
  49. package/dist/ui/Icons/Icons.jsx +78 -0
  50. package/dist/ui/Icons/LottieAnimation.jsx +64 -0
  51. package/dist/utilities/extractPromptAttachments.d.ts +2 -2
  52. package/dist/utilities/extractPromptAttachments.js.map +1 -1
  53. package/dist/utilities/fieldToJsonSchema.d.ts +37 -0
  54. package/dist/utilities/fieldToJsonSchema.js +274 -0
  55. package/dist/utilities/fieldToJsonSchema.js.map +1 -0
  56. package/dist/utilities/getFieldBySchemaPath.d.ts +12 -1
  57. package/dist/utilities/getFieldBySchemaPath.js +63 -29
  58. package/dist/utilities/getFieldBySchemaPath.js.map +1 -1
  59. package/package.json +2 -1
  60. package/dist/ai/models/anthropic/generateRichText.d.ts +0 -1
  61. package/dist/ai/models/anthropic/generateRichText.js +0 -36
  62. package/dist/ai/models/anthropic/generateRichText.js.map +0 -1
  63. package/dist/ai/models/openai/generateRichText.d.ts +0 -1
  64. package/dist/ai/models/openai/generateRichText.js +0 -37
  65. package/dist/ai/models/openai/generateRichText.js.map +0 -1
@@ -19,7 +19,7 @@ const getActiveComponent = (ac)=>{
19
19
  }
20
20
  };
21
21
  export const useMenu = (menuEvents, options)=>{
22
- const { type: fieldType, path: pathFromContext } = useFieldProps();
22
+ const { field: { type: fieldType } = {}, path: pathFromContext } = useFieldProps();
23
23
  const field = useField({
24
24
  path: pathFromContext ?? ''
25
25
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/useMenu.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport React, { useEffect, useMemo, useState } from 'react'\n\nimport type { ActionMenuItems, UseMenuEvents, UseMenuOptions } from '../../../../types.js'\n\nimport { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js'\nimport { Compose, Proofread, Rephrase } from './items.js'\nimport { menuItemsMap } from './itemsMap.js'\nimport styles from './menu.module.scss'\n\nconst getActiveComponent = (ac: ActionMenuItems) => {\n switch (ac) {\n case 'Compose':\n return Compose\n case 'Proofread':\n return Proofread\n case 'Rephrase':\n return Rephrase\n default:\n return Rephrase\n }\n}\n\nexport const useMenu = (menuEvents: UseMenuEvents, options: UseMenuOptions) => {\n const { type: fieldType, path: pathFromContext } = useFieldProps()\n const field = useField({ path: pathFromContext ?? '' })\n const [activeComponent, setActiveComponent] = useState<ActionMenuItems>('Rephrase')\n\n const { initialValue, value } = field\n\n useEffect(() => {\n if (!value) {\n setActiveComponent('Compose')\n return\n }\n\n if (menuItemsMap.some((i) => i.excludedFor?.includes(fieldType ?? ''))) {\n setActiveComponent('Compose')\n return\n }\n\n if (typeof value === 'string' && value !== initialValue) {\n setActiveComponent('Proofread')\n } else {\n setActiveComponent('Rephrase')\n }\n }, [initialValue, value, fieldType])\n\n const MemoizedActiveComponent = useMemo(() => {\n return ({ isLoading, stop }: { isLoading: boolean; stop: () => void }) => {\n const ActiveComponent = getActiveComponent(activeComponent)\n const activeItem = menuItemsMap.find((i) => i.name === activeComponent)!\n return (\n <ActiveComponent\n hideIcon\n onClick={(data: unknown) => {\n if (!isLoading) {\n const trigger = menuEvents[`on${activeComponent}`]\n if (typeof trigger === 'function') {\n trigger(data)\n } else {\n console.error('No trigger found for', activeComponent)\n }\n } else {\n stop()\n }\n }}\n title={isLoading ? 'Click to stop' : activeItem.name}\n >\n {isLoading && activeItem.loadingText}\n </ActiveComponent>\n )\n }\n }, [activeComponent, menuEvents])\n\n const filteredMenuItems = useMemo(\n () =>\n menuItemsMap.filter((i) => {\n if (i.name === 'Settings' && !options.isConfigAllowed) {\n return false\n } // Disable settings if a user role is not permitted\n return i.name !== activeComponent && !i.excludedFor?.includes(fieldType ?? '')\n }),\n [activeComponent, fieldType, options.isConfigAllowed],\n )\n\n const MemoizedMenu = useMemo(() => {\n return ({ isLoading, onClose }: { isLoading: boolean; onClose: () => void }) => (\n <div className={styles.menu}>\n {filteredMenuItems.map((i) => {\n const Action = i.component\n return (\n <Action\n disabled={isLoading}\n key={i.name}\n onClick={(data: unknown) => {\n if (i.name !== 'Settings') {\n setActiveComponent(i.name)\n }\n\n menuEvents[`on${i.name}`]?.(data)\n onClose()\n }}\n >\n {isLoading && i.loadingText}\n </Action>\n )\n })}\n </div>\n )\n }, [filteredMenuItems, menuEvents])\n\n return {\n ActiveComponent: MemoizedActiveComponent,\n Menu: MemoizedMenu,\n }\n}\n"],"names":["useField","React","useEffect","useMemo","useState","useFieldProps","Compose","Proofread","Rephrase","menuItemsMap","styles","getActiveComponent","ac","useMenu","menuEvents","options","type","fieldType","path","pathFromContext","field","activeComponent","setActiveComponent","initialValue","value","some","i","excludedFor","includes","MemoizedActiveComponent","isLoading","stop","ActiveComponent","activeItem","find","name","hideIcon","onClick","data","trigger","console","error","title","loadingText","filteredMenuItems","filter","isConfigAllowed","MemoizedMenu","onClose","div","className","menu","map","Action","component","disabled","Menu"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/useMenu.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport React, { useEffect, useMemo, useState } from 'react'\n\nimport type { ActionMenuItems, UseMenuEvents, UseMenuOptions } from '../../../../types.js'\n\nimport { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js'\nimport { Compose, Proofread, Rephrase } from './items.js'\nimport { menuItemsMap } from './itemsMap.js'\nimport styles from './menu.module.scss'\n\nconst getActiveComponent = (ac: ActionMenuItems) => {\n switch (ac) {\n case 'Compose':\n return Compose\n case 'Proofread':\n return Proofread\n case 'Rephrase':\n return Rephrase\n default:\n return Rephrase\n }\n}\n\nexport const useMenu = (menuEvents: UseMenuEvents, options: UseMenuOptions) => {\n const { field:{ type: fieldType } = {}, path: pathFromContext } = useFieldProps()\n const field = useField({ path: pathFromContext ?? '' })\n const [activeComponent, setActiveComponent] = useState<ActionMenuItems>('Rephrase')\n\n const { initialValue, value } = field\n\n useEffect(() => {\n if (!value) {\n setActiveComponent('Compose')\n return\n }\n\n if (menuItemsMap.some((i) => i.excludedFor?.includes(fieldType ?? ''))) {\n setActiveComponent('Compose')\n return\n }\n\n if (typeof value === 'string' && value !== initialValue) {\n setActiveComponent('Proofread')\n } else {\n setActiveComponent('Rephrase')\n }\n }, [initialValue, value, fieldType])\n\n const MemoizedActiveComponent = useMemo(() => {\n return ({ isLoading, stop }: { isLoading: boolean; stop: () => void }) => {\n const ActiveComponent = getActiveComponent(activeComponent)\n const activeItem = menuItemsMap.find((i) => i.name === activeComponent)!\n return (\n <ActiveComponent\n hideIcon\n onClick={(data: unknown) => {\n if (!isLoading) {\n const trigger = menuEvents[`on${activeComponent}`]\n if (typeof trigger === 'function') {\n trigger(data)\n } else {\n console.error('No trigger found for', activeComponent)\n }\n } else {\n stop()\n }\n }}\n title={isLoading ? 'Click to stop' : activeItem.name}\n >\n {isLoading && activeItem.loadingText}\n </ActiveComponent>\n )\n }\n }, [activeComponent, menuEvents])\n\n const filteredMenuItems = useMemo(\n () =>\n menuItemsMap.filter((i) => {\n if (i.name === 'Settings' && !options.isConfigAllowed) {\n return false\n } // Disable settings if a user role is not permitted\n return i.name !== activeComponent && !i.excludedFor?.includes(fieldType ?? '')\n }),\n [activeComponent, fieldType, options.isConfigAllowed],\n )\n\n const MemoizedMenu = useMemo(() => {\n return ({ isLoading, onClose }: { isLoading: boolean; onClose: () => void }) => (\n <div className={styles.menu}>\n {filteredMenuItems.map((i) => {\n const Action = i.component\n return (\n <Action\n disabled={isLoading}\n key={i.name}\n onClick={(data: unknown) => {\n if (i.name !== 'Settings') {\n setActiveComponent(i.name)\n }\n\n menuEvents[`on${i.name}`]?.(data)\n onClose()\n }}\n >\n {isLoading && i.loadingText}\n </Action>\n )\n })}\n </div>\n )\n }, [filteredMenuItems, menuEvents])\n\n return {\n ActiveComponent: MemoizedActiveComponent,\n Menu: MemoizedMenu,\n }\n}\n"],"names":["useField","React","useEffect","useMemo","useState","useFieldProps","Compose","Proofread","Rephrase","menuItemsMap","styles","getActiveComponent","ac","useMenu","menuEvents","options","field","type","fieldType","path","pathFromContext","activeComponent","setActiveComponent","initialValue","value","some","i","excludedFor","includes","MemoizedActiveComponent","isLoading","stop","ActiveComponent","activeItem","find","name","hideIcon","onClick","data","trigger","console","error","title","loadingText","filteredMenuItems","filter","isConfigAllowed","MemoizedMenu","onClose","div","className","menu","map","Action","component","disabled","Menu"],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+ import { useField } from '@payloadcms/ui';
3
+ import React, { useEffect, useMemo, useState } from 'react';
4
+ import { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js';
5
+ import { Compose, Proofread, Rephrase } from './items.js';
6
+ import { menuItemsMap } from './itemsMap.js';
7
+ import styles from './menu.module.scss';
8
+ const getActiveComponent = (ac) => {
9
+ switch (ac) {
10
+ case 'Compose':
11
+ return Compose;
12
+ case 'Proofread':
13
+ return Proofread;
14
+ case 'Rephrase':
15
+ return Rephrase;
16
+ default:
17
+ return Rephrase;
18
+ }
19
+ };
20
+ export const useMenu = (menuEvents, options) => {
21
+ const { field: { type: fieldType } = {}, path: pathFromContext } = useFieldProps();
22
+ const field = useField({ path: pathFromContext ?? '' });
23
+ const [activeComponent, setActiveComponent] = useState('Rephrase');
24
+ const { initialValue, value } = field;
25
+ useEffect(() => {
26
+ if (!value) {
27
+ setActiveComponent('Compose');
28
+ return;
29
+ }
30
+ if (menuItemsMap.some((i) => i.excludedFor?.includes(fieldType ?? ''))) {
31
+ setActiveComponent('Compose');
32
+ return;
33
+ }
34
+ if (typeof value === 'string' && value !== initialValue) {
35
+ setActiveComponent('Proofread');
36
+ }
37
+ else {
38
+ setActiveComponent('Rephrase');
39
+ }
40
+ }, [initialValue, value, fieldType]);
41
+ const MemoizedActiveComponent = useMemo(() => {
42
+ return ({ isLoading, stop }) => {
43
+ const ActiveComponent = getActiveComponent(activeComponent);
44
+ const activeItem = menuItemsMap.find((i) => i.name === activeComponent);
45
+ return (<ActiveComponent hideIcon onClick={(data) => {
46
+ if (!isLoading) {
47
+ const trigger = menuEvents[`on${activeComponent}`];
48
+ if (typeof trigger === 'function') {
49
+ trigger(data);
50
+ }
51
+ else {
52
+ console.error('No trigger found for', activeComponent);
53
+ }
54
+ }
55
+ else {
56
+ stop();
57
+ }
58
+ }} title={isLoading ? 'Click to stop' : activeItem.name}>
59
+ {isLoading && activeItem.loadingText}
60
+ </ActiveComponent>);
61
+ };
62
+ }, [activeComponent, menuEvents]);
63
+ const filteredMenuItems = useMemo(() => menuItemsMap.filter((i) => {
64
+ if (i.name === 'Settings' && !options.isConfigAllowed) {
65
+ return false;
66
+ } // Disable settings if a user role is not permitted
67
+ return i.name !== activeComponent && !i.excludedFor?.includes(fieldType ?? '');
68
+ }), [activeComponent, fieldType, options.isConfigAllowed]);
69
+ const MemoizedMenu = useMemo(() => {
70
+ return ({ isLoading, onClose }) => (<div className={styles.menu}>
71
+ {filteredMenuItems.map((i) => {
72
+ const Action = i.component;
73
+ return (<Action disabled={isLoading} key={i.name} onClick={(data) => {
74
+ if (i.name !== 'Settings') {
75
+ setActiveComponent(i.name);
76
+ }
77
+ menuEvents[`on${i.name}`]?.(data);
78
+ onClose();
79
+ }}>
80
+ {isLoading && i.loadingText}
81
+ </Action>);
82
+ })}
83
+ </div>);
84
+ }, [filteredMenuItems, menuEvents]);
85
+ return {
86
+ ActiveComponent: MemoizedActiveComponent,
87
+ Menu: MemoizedMenu,
88
+ };
89
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Initialize document-level listeners to track the active field container.
3
+ * When a container is active, it receives the 'ai-plugin-active' class.
4
+ */
5
+ export declare const useActiveFieldTracking: () => void;
@@ -0,0 +1,225 @@
1
+ 'use client';
2
+ import { useEffect } from 'react';
3
+ /**
4
+ * Allowed field type classes that should show the active state
5
+ */ const ALLOWED_FIELD_TYPES = [
6
+ 'upload',
7
+ 'text',
8
+ 'textarea',
9
+ 'rich-text-lexical'
10
+ ];
11
+ let currentContainer = null;
12
+ let rafId = null // Track RAF to cancel if needed
13
+ ;
14
+ /**
15
+ * Safely escape CSS selector values
16
+ */ const cssEscape = (value)=>{
17
+ if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
18
+ return CSS.escape(value);
19
+ }
20
+ return value.replace(/([ #;?%&,.+*~':"!^$[\]()=>|/@])/g, '\\$1');
21
+ };
22
+ /**
23
+ * Find container from React Select dropdown elements
24
+ */ const findContainerFromReactSelect = (target)=>{
25
+ const listbox = target.closest('[role="listbox"]');
26
+ if (!listbox?.id) {
27
+ return null;
28
+ }
29
+ const id = cssEscape(listbox.id);
30
+ const selector = `[aria-controls="${id}"], [aria-owns="${id}"]`;
31
+ const control = document.querySelector(selector);
32
+ return control?.closest('.field-type') ?? null;
33
+ };
34
+ /**
35
+ * Check if a container has one of the allowed field type classes
36
+ */ const isAllowedFieldType = (container)=>{
37
+ return ALLOWED_FIELD_TYPES.some((type)=>container.classList.contains(type) || container.classList.contains(`field-type-${type}`));
38
+ };
39
+ /**
40
+ * Resolve the .field-type container for a given event target
41
+ * Only returns containers that match allowed field types
42
+ */ const resolveContainerFromTarget = (target)=>{
43
+ if (!(target instanceof HTMLElement)) {
44
+ return null;
45
+ }
46
+ // Check for direct parent first
47
+ let container = target.closest('.field-type');
48
+ // If not found, fall back to React Select logic
49
+ if (!container) {
50
+ container = findContainerFromReactSelect(target);
51
+ }
52
+ // Only return if it's an allowed field type
53
+ if (container && isAllowedFieldType(container)) {
54
+ return container;
55
+ }
56
+ return null;
57
+ };
58
+ /**
59
+ * Update the active container and toggle CSS class
60
+ * - Avoids acting on disconnected nodes
61
+ * - Avoids redundant class work
62
+ */ const setActiveContainer = (next)=>{
63
+ // Normalize both references against disconnected nodes
64
+ if (currentContainer && !currentContainer.isConnected) {
65
+ currentContainer = null;
66
+ }
67
+ if (next && !next.isConnected) {
68
+ next = null;
69
+ }
70
+ if (currentContainer === next) {
71
+ return;
72
+ }
73
+ currentContainer?.classList.remove('ai-plugin-active');
74
+ if (next) {
75
+ next.classList.add('ai-plugin-active');
76
+ }
77
+ currentContainer = next;
78
+ };
79
+ const clearActiveContainer = ()=>{
80
+ if (currentContainer) {
81
+ currentContainer.classList.remove('ai-plugin-active');
82
+ currentContainer = null;
83
+ }
84
+ // Cancel any pending RAF
85
+ if (rafId !== null) {
86
+ cancelAnimationFrame(rafId);
87
+ rafId = null;
88
+ }
89
+ };
90
+ const isInteractiveElement = (element)=>{
91
+ const tagName = element.tagName.toLowerCase();
92
+ const interactiveTags = [
93
+ 'input',
94
+ 'textarea',
95
+ 'select',
96
+ 'button'
97
+ ];
98
+ if (interactiveTags.includes(tagName)) {
99
+ return true;
100
+ }
101
+ // Check for contenteditable
102
+ if (element.isContentEditable) {
103
+ return true;
104
+ }
105
+ // Check for elements with role="textbox" or role="combobox" (React Select)
106
+ const role = element.getAttribute('role');
107
+ if (role && [
108
+ 'combobox',
109
+ 'listbox',
110
+ 'searchbox',
111
+ 'textbox'
112
+ ].includes(role)) {
113
+ return true;
114
+ }
115
+ return false;
116
+ };
117
+ /**
118
+ * Handle focus events - only activate if focus is on an interactive element within .field-type
119
+ */ const onFocusIn = (e)=>{
120
+ const target = e.target;
121
+ if (!(target instanceof HTMLElement)) {
122
+ return;
123
+ }
124
+ // Early exit if we're already inside the current container
125
+ if (currentContainer?.isConnected && currentContainer.contains(target)) {
126
+ return;
127
+ }
128
+ // Only activate if the focused element is actually interactive
129
+ if (!isInteractiveElement(target)) {
130
+ return;
131
+ }
132
+ const container = resolveContainerFromTarget(target);
133
+ setActiveContainer(container);
134
+ };
135
+ /**
136
+ * Handle pointer/mouse events - only switch when clicking a different .field-type
137
+ */ const onPointerDown = (e)=>{
138
+ const target = e.target;
139
+ if (!(target instanceof HTMLElement)) {
140
+ return;
141
+ }
142
+ // Early exit if clicking within current container
143
+ if (currentContainer?.isConnected && currentContainer.contains(target)) {
144
+ return;
145
+ }
146
+ const container = resolveContainerFromTarget(target);
147
+ setActiveContainer(container);
148
+ };
149
+ /**
150
+ * Handle keyboard navigation (Tab key)
151
+ */ const onKeyDown = (e)=>{
152
+ if (e.key !== 'Tab') {
153
+ return;
154
+ }
155
+ // Cancel any pending RAF to prevent queuing
156
+ if (rafId !== null) {
157
+ cancelAnimationFrame(rafId);
158
+ }
159
+ // Defer until after focus has shifted
160
+ rafId = requestAnimationFrame(()=>{
161
+ rafId = null;
162
+ const container = resolveContainerFromTarget(document.activeElement);
163
+ setActiveContainer(container);
164
+ });
165
+ };
166
+ /**
167
+ * Handle visibility changes to properly cleanup when page is hidden
168
+ */ const onVisibilityChange = ()=>{
169
+ if (typeof document !== 'undefined' && document.hidden) {
170
+ // Clear active state and cancel pending operations
171
+ clearActiveContainer();
172
+ }
173
+ };
174
+ /**
175
+ * Initialize document-level listeners to track the active field container.
176
+ * When a container is active, it receives the 'ai-plugin-active' class.
177
+ */ export const useActiveFieldTracking = ()=>{
178
+ useEffect(()=>{
179
+ if (typeof window === 'undefined') {
180
+ return;
181
+ }
182
+ const pluginWindow = window;
183
+ // Track number of mounted users of the hook
184
+ pluginWindow.__aiComposeTrackingCount = (pluginWindow.__aiComposeTrackingCount ?? 0) + 1;
185
+ // Initialize listeners only once
186
+ if (!pluginWindow.__aiComposeTracking) {
187
+ const controller = new AbortController();
188
+ pluginWindow.__aiComposeTrackingController = controller;
189
+ // Use capture for early handling
190
+ document.addEventListener('focusin', onFocusIn, {
191
+ capture: true,
192
+ signal: controller.signal
193
+ });
194
+ document.addEventListener('pointerdown', onPointerDown, {
195
+ capture: true,
196
+ passive: true,
197
+ signal: controller.signal
198
+ });
199
+ document.addEventListener('keydown', onKeyDown, {
200
+ capture: true,
201
+ signal: controller.signal
202
+ });
203
+ document.addEventListener('visibilitychange', onVisibilityChange, {
204
+ signal: controller.signal
205
+ });
206
+ pluginWindow.__aiComposeTracking = true;
207
+ }
208
+ return ()=>{
209
+ // Decrement and cleanup when the last user unmounts
210
+ pluginWindow.__aiComposeTrackingCount = (pluginWindow.__aiComposeTrackingCount ?? 1) - 1;
211
+ if ((pluginWindow.__aiComposeTrackingCount ?? 0) <= 0) {
212
+ // Atomically remove all listeners
213
+ pluginWindow.__aiComposeTrackingController?.abort();
214
+ pluginWindow.__aiComposeTrackingController = undefined;
215
+ // Clear active state and cancel pending operations
216
+ clearActiveContainer();
217
+ // Reset all state
218
+ pluginWindow.__aiComposeTracking = false;
219
+ pluginWindow.__aiComposeTrackingCount = 0;
220
+ }
221
+ };
222
+ }, []);
223
+ };
224
+
225
+ //# sourceMappingURL=useActiveFieldTracking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/ui/Compose/hooks/useActiveFieldTracking.ts"],"sourcesContent":["'use client'\n\nimport { useEffect } from 'react'\n\n/**\n * Allowed field type classes that should show the active state\n */\nconst ALLOWED_FIELD_TYPES = ['upload', 'text', 'textarea', 'rich-text-lexical']\n\nlet currentContainer: HTMLElement | null = null\nlet rafId: null | number = null // Track RAF to cancel if needed\n\n/**\n * Safely escape CSS selector values\n */\nconst cssEscape = (value: string): string => {\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {\n return CSS.escape(value)\n }\n return value.replace(/([ #;?%&,.+*~':\"!^$[\\]()=>|/@])/g, '\\\\$1')\n}\n\n/**\n * Find container from React Select dropdown elements\n */\nconst findContainerFromReactSelect = (target: HTMLElement): HTMLElement | null => {\n const listbox = target.closest<HTMLElement>('[role=\"listbox\"]')\n if (!listbox?.id) {\n return null\n }\n\n const id = cssEscape(listbox.id)\n const selector = `[aria-controls=\"${id}\"], [aria-owns=\"${id}\"]`\n const control = document.querySelector<HTMLElement>(selector)\n\n return control?.closest<HTMLElement>('.field-type') ?? null\n}\n\n/**\n * Check if a container has one of the allowed field type classes\n */\nconst isAllowedFieldType = (container: HTMLElement): boolean => {\n return ALLOWED_FIELD_TYPES.some(\n (type) =>\n container.classList.contains(type) || container.classList.contains(`field-type-${type}`),\n )\n}\n\n/**\n * Resolve the .field-type container for a given event target\n * Only returns containers that match allowed field types\n */\nconst resolveContainerFromTarget = (target: EventTarget | null): HTMLElement | null => {\n if (!(target instanceof HTMLElement)) {\n return null\n }\n\n // Check for direct parent first\n let container = target.closest<HTMLElement>('.field-type')\n\n // If not found, fall back to React Select logic\n if (!container) {\n container = findContainerFromReactSelect(target)\n }\n\n // Only return if it's an allowed field type\n if (container && isAllowedFieldType(container)) {\n return container\n }\n\n return null\n}\n\n/**\n * Update the active container and toggle CSS class\n * - Avoids acting on disconnected nodes\n * - Avoids redundant class work\n */\nconst setActiveContainer = (next: HTMLElement | null): void => {\n // Normalize both references against disconnected nodes\n if (currentContainer && !currentContainer.isConnected) {\n currentContainer = null\n }\n if (next && !next.isConnected) {\n next = null\n }\n\n if (currentContainer === next) {\n return\n }\n\n currentContainer?.classList.remove('ai-plugin-active')\n if (next) {\n next.classList.add('ai-plugin-active')\n }\n currentContainer = next\n}\n\nconst clearActiveContainer = (): void => {\n if (currentContainer) {\n currentContainer.classList.remove('ai-plugin-active')\n currentContainer = null\n }\n\n // Cancel any pending RAF\n if (rafId !== null) {\n cancelAnimationFrame(rafId)\n rafId = null\n }\n}\n\nconst isInteractiveElement = (element: HTMLElement): boolean => {\n const tagName = element.tagName.toLowerCase()\n const interactiveTags = ['input', 'textarea', 'select', 'button']\n\n if (interactiveTags.includes(tagName)) {\n return true\n }\n\n // Check for contenteditable\n if (element.isContentEditable) {\n return true\n }\n\n // Check for elements with role=\"textbox\" or role=\"combobox\" (React Select)\n const role = element.getAttribute('role')\n if (role && ['combobox', 'listbox', 'searchbox', 'textbox'].includes(role)) {\n return true\n }\n\n return false\n}\n\n/**\n * Handle focus events - only activate if focus is on an interactive element within .field-type\n */\nconst onFocusIn = (e: FocusEvent): void => {\n const target = e.target\n if (!(target instanceof HTMLElement)) {\n return\n }\n\n // Early exit if we're already inside the current container\n if (currentContainer?.isConnected && currentContainer.contains(target)) {\n return\n }\n\n // Only activate if the focused element is actually interactive\n if (!isInteractiveElement(target)) {\n return\n }\n\n const container = resolveContainerFromTarget(target)\n setActiveContainer(container)\n}\n\n/**\n * Handle pointer/mouse events - only switch when clicking a different .field-type\n */\nconst onPointerDown = (e: PointerEvent): void => {\n const target = e.target\n if (!(target instanceof HTMLElement)) {\n return\n }\n\n // Early exit if clicking within current container\n if (currentContainer?.isConnected && currentContainer.contains(target)) {\n return\n }\n\n const container = resolveContainerFromTarget(target)\n setActiveContainer(container)\n}\n\n/**\n * Handle keyboard navigation (Tab key)\n */\nconst onKeyDown = (e: KeyboardEvent): void => {\n if (e.key !== 'Tab') {\n return\n }\n\n // Cancel any pending RAF to prevent queuing\n if (rafId !== null) {\n cancelAnimationFrame(rafId)\n }\n\n // Defer until after focus has shifted\n rafId = requestAnimationFrame(() => {\n rafId = null\n const container = resolveContainerFromTarget(document.activeElement)\n setActiveContainer(container)\n })\n}\n\n/**\n * Handle visibility changes to properly cleanup when page is hidden\n */\nconst onVisibilityChange = (): void => {\n if (typeof document !== 'undefined' && document.hidden) {\n // Clear active state and cancel pending operations\n clearActiveContainer()\n }\n}\n\n/**\n * Initialize document-level listeners to track the active field container.\n * When a container is active, it receives the 'ai-plugin-active' class.\n */\nexport const useActiveFieldTracking = (): void => {\n useEffect(() => {\n if (typeof window === 'undefined') {\n return\n }\n\n const pluginWindow = window as {\n __aiComposeTracking?: boolean\n __aiComposeTrackingController?: AbortController\n __aiComposeTrackingCount?: number\n } & Window\n\n // Track number of mounted users of the hook\n pluginWindow.__aiComposeTrackingCount = (pluginWindow.__aiComposeTrackingCount ?? 0) + 1\n\n // Initialize listeners only once\n if (!pluginWindow.__aiComposeTracking) {\n const controller = new AbortController()\n pluginWindow.__aiComposeTrackingController = controller\n\n // Use capture for early handling\n document.addEventListener('focusin', onFocusIn, {\n capture: true,\n signal: controller.signal,\n })\n document.addEventListener('pointerdown', onPointerDown, {\n capture: true,\n passive: true,\n signal: controller.signal,\n })\n document.addEventListener('keydown', onKeyDown, {\n capture: true,\n signal: controller.signal,\n })\n document.addEventListener('visibilitychange', onVisibilityChange, {\n signal: controller.signal,\n })\n\n pluginWindow.__aiComposeTracking = true\n }\n\n return () => {\n // Decrement and cleanup when the last user unmounts\n pluginWindow.__aiComposeTrackingCount = (pluginWindow.__aiComposeTrackingCount ?? 1) - 1\n\n if ((pluginWindow.__aiComposeTrackingCount ?? 0) <= 0) {\n // Atomically remove all listeners\n pluginWindow.__aiComposeTrackingController?.abort()\n pluginWindow.__aiComposeTrackingController = undefined\n\n // Clear active state and cancel pending operations\n clearActiveContainer()\n\n // Reset all state\n pluginWindow.__aiComposeTracking = false\n pluginWindow.__aiComposeTrackingCount = 0\n }\n }\n }, [])\n}\n"],"names":["useEffect","ALLOWED_FIELD_TYPES","currentContainer","rafId","cssEscape","value","CSS","escape","replace","findContainerFromReactSelect","target","listbox","closest","id","selector","control","document","querySelector","isAllowedFieldType","container","some","type","classList","contains","resolveContainerFromTarget","HTMLElement","setActiveContainer","next","isConnected","remove","add","clearActiveContainer","cancelAnimationFrame","isInteractiveElement","element","tagName","toLowerCase","interactiveTags","includes","isContentEditable","role","getAttribute","onFocusIn","e","onPointerDown","onKeyDown","key","requestAnimationFrame","activeElement","onVisibilityChange","hidden","useActiveFieldTracking","window","pluginWindow","__aiComposeTrackingCount","__aiComposeTracking","controller","AbortController","__aiComposeTrackingController","addEventListener","capture","signal","passive","abort","undefined"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { useCompletion, experimental_useObject as useObject } from '@ai-sdk/react';
1
+ import { experimental_useObject as useObject } from '@ai-sdk/react';
2
2
  import { useEditorConfigContext } from '@payloadcms/richtext-lexical/client';
3
3
  import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui';
4
4
  import { jsonSchema } from 'ai';
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useRef } from 'react';
6
6
  import { PLUGIN_API_ENDPOINT_GENERATE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD, PLUGIN_INSTRUCTIONS_TABLE, PLUGIN_NAME } from '../../../defaults.js';
7
7
  import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js';
8
8
  import { editorSchemaValidator } from '../../../utilities/editorSchemaValidator.js';
9
+ import { fieldToJsonSchema } from '../../../utilities/fieldToJsonSchema.js';
9
10
  import { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js';
10
11
  import { useHistory } from './useHistory.js';
11
12
  export const useGenerate = ({ instructionId })=>{
@@ -17,7 +18,7 @@ export const useGenerate = ({ instructionId })=>{
17
18
  }, [
18
19
  instructionId
19
20
  ]);
20
- const { type, path: pathFromContext } = useFieldProps();
21
+ const { field, path: pathFromContext } = useFieldProps();
21
22
  const editorConfigContext = useEditorConfigContext();
22
23
  const { editor } = editorConfigContext;
23
24
  const { config } = useConfig();
@@ -56,6 +57,24 @@ export const useGenerate = ({ instructionId })=>{
56
57
  }), [
57
58
  memoizedValidator
58
59
  ]);
60
+ // Active JSON schema for useObject based on field type
61
+ const activeSchema = useMemo(()=>{
62
+ const f = field;
63
+ const fieldType = f?.type;
64
+ if (fieldType === 'richText') {
65
+ return memoizedSchema;
66
+ }
67
+ if (f && f.name && fieldType) {
68
+ const schemaJson = fieldToJsonSchema(f);
69
+ if (schemaJson && Object.keys(schemaJson).length > 0) {
70
+ return jsonSchema(schemaJson);
71
+ }
72
+ }
73
+ return undefined;
74
+ }, [
75
+ field,
76
+ memoizedSchema
77
+ ]);
59
78
  const { isLoading: loadingObject, object, stop: objectStop, submit } = useObject({
60
79
  api: `/api${PLUGIN_API_ENDPOINT_GENERATE}`,
61
80
  onError: (error)=>{
@@ -63,50 +82,35 @@ export const useGenerate = ({ instructionId })=>{
63
82
  console.error('Error generating object:', error);
64
83
  },
65
84
  onFinish: (result)=>{
66
- if (result.object) {
67
- setHistory(result.object);
68
- setValue(result.object);
85
+ if (result.object && field) {
86
+ if (field.type === 'richText') {
87
+ setHistory(result.object);
88
+ setValue(result.object);
89
+ } else if ('name' in field) {
90
+ setHistory(result.object[field.name]);
91
+ setValue(result.object[field.name]);
92
+ }
69
93
  } else {
70
- console.log('onFinish: result ', result);
94
+ console.log('onFinish: result, field ', result, field);
71
95
  }
72
96
  },
73
- schema: memoizedSchema
97
+ schema: activeSchema
74
98
  });
75
99
  useEffect(()=>{
76
100
  if (!object) {
77
101
  return;
78
102
  }
79
103
  requestAnimationFrame(()=>{
80
- // TODO: Temporary disabled pre validation, sometimes it fails to validate
81
- // const validateObject = await memoizedSchema?.validate?.(object)
82
- // if (validateObject?.success) {
83
- setSafeLexicalState(object, editor);
84
- // }
104
+ if (field?.type === 'richText') {
105
+ setSafeLexicalState(object, editor);
106
+ } else if (field && 'name' in field && object[field.name]) {
107
+ setValue(object[field.name]);
108
+ }
85
109
  });
86
110
  }, [
87
111
  object,
88
- editor
89
- ]);
90
- const { complete, completion, isLoading: loadingCompletion, stop: completionStop } = useCompletion({
91
- api: `${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE}`,
92
- onError: (error)=>{
93
- toast.error(`Failed to generate: ${error.message}`);
94
- console.error('Error generating text:', error);
95
- },
96
- onFinish: (prompt, result)=>{
97
- setHistory(result);
98
- },
99
- streamProtocol: 'data'
100
- });
101
- useEffect(()=>{
102
- if (!completion) {
103
- return;
104
- }
105
- requestAnimationFrame(()=>{
106
- setValue(completion);
107
- });
108
- }, [
109
- completion
112
+ editor,
113
+ field
110
114
  ]);
111
115
  const streamObject = useCallback(({ action = 'Compose', params })=>{
112
116
  const doc = getData();
@@ -130,31 +134,6 @@ export const useGenerate = ({ instructionId })=>{
130
134
  instructionIdRef,
131
135
  documentId
132
136
  ]);
133
- const streamText = useCallback(async ({ action = 'Compose', params })=>{
134
- const doc = getData();
135
- const currentInstructionId = instructionIdRef.current;
136
- const options = {
137
- action,
138
- actionParams: params,
139
- instructionId: currentInstructionId
140
- };
141
- await complete('', {
142
- body: {
143
- doc: {
144
- ...doc,
145
- id: documentId
146
- },
147
- locale: localFromContext?.code,
148
- options
149
- }
150
- });
151
- }, [
152
- getData,
153
- localFromContext?.code,
154
- instructionIdRef,
155
- complete,
156
- documentId
157
- ]);
158
137
  const generateUpload = useCallback(async ()=>{
159
138
  const doc = getData();
160
139
  const currentInstructionId = instructionIdRef.current;
@@ -201,39 +180,27 @@ export const useGenerate = ({ instructionId })=>{
201
180
  collectionSlug
202
181
  ]);
203
182
  const generate = useCallback(async (options)=>{
204
- if (type === 'richText') {
205
- return streamObject(options ?? {
206
- action: 'Compose'
207
- });
208
- }
209
- if ([
210
- 'text',
211
- 'textarea'
212
- ].includes(type ?? '') && type) {
213
- return streamText(options ?? {
214
- action: 'Compose'
215
- });
216
- }
217
- if (type === 'upload') {
183
+ if (field?.type === 'upload') {
218
184
  return generateUpload();
219
185
  }
186
+ // All supported types use structured object generation when schema is provided server-side
187
+ return streamObject(options ?? {
188
+ action: 'Compose'
189
+ });
220
190
  }, [
221
191
  generateUpload,
222
192
  streamObject,
223
- streamText,
224
- type
193
+ field
225
194
  ]);
226
195
  const stop = useCallback(()=>{
227
196
  console.log('Stopping...');
228
197
  objectStop();
229
- completionStop();
230
198
  }, [
231
- objectStop,
232
- completionStop
199
+ objectStop
233
200
  ]);
234
201
  return {
235
202
  generate,
236
- isLoading: loadingCompletion || loadingObject,
203
+ isLoading: loadingObject,
237
204
  stop
238
205
  };
239
206
  };