@ai-stack/payloadcms 3.76.0-beta.0 → 3.76.0-beta.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/ai/core/media/image/handlers/multimodal.js +13 -33
- package/dist/ai/core/media/image/handlers/multimodal.js.map +1 -1
- package/dist/ai/core/media/image/handlers/standard.js +10 -7
- package/dist/ai/core/media/image/handlers/standard.js.map +1 -1
- package/dist/ai/core/media/speech/generateSpeech.js +8 -8
- package/dist/ai/core/media/speech/generateSpeech.js.map +1 -1
- package/dist/ai/core/media/types.d.ts +1 -1
- package/dist/ai/core/media/types.js.map +1 -1
- package/dist/ai/providers/blocks/google.js +1 -0
- package/dist/ai/providers/blocks/google.js.map +1 -1
- package/dist/ai/providers/registry.js +2 -2
- package/dist/ai/providers/registry.js.map +1 -1
- package/dist/collections/AIProviders.d.ts +2 -0
- package/dist/collections/{AISettings.js → AIProviders.js} +7 -6
- package/dist/collections/AIProviders.js.map +1 -0
- package/dist/collections/Instructions.js +8 -4
- package/dist/collections/Instructions.js.map +1 -1
- package/dist/defaults.js +1 -1
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/fetchFields.js +1 -1
- package/dist/endpoints/fetchFields.js.map +1 -1
- package/dist/endpoints/fetchVoices.js +1 -1
- package/dist/endpoints/fetchVoices.js.map +1 -1
- package/dist/endpoints/index.js +56 -21
- package/dist/endpoints/index.js.map +1 -1
- package/dist/exports/client.d.ts +1 -1
- package/dist/exports/client.js +1 -1
- package/dist/exports/client.js.map +1 -1
- package/dist/plugin.js +2 -2
- package/dist/plugin.js.map +1 -1
- package/dist/providers/InstructionsProvider/useInstructions.js +1 -1
- package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/types.js.map +1 -1
- package/dist/ui/AIConfigDashboard/index.d.ts +1 -1
- package/dist/ui/AIConfigDashboard/index.js +16 -14
- package/dist/ui/AIConfigDashboard/index.js.map +1 -1
- package/dist/ui/AIConfigDashboard/index.jsx +18 -13
- package/dist/ui/Compose/hooks/menu/TranslateMenu.d.ts +5 -0
- package/dist/ui/Compose/hooks/menu/TranslateMenu.js +45 -4
- package/dist/ui/Compose/hooks/menu/TranslateMenu.js.map +1 -1
- package/dist/ui/Compose/hooks/menu/TranslateMenu.jsx +41 -5
- package/dist/ui/Compose/hooks/menu/menu.module.scss +4 -1
- package/dist/ui/Compose/hooks/useActiveFieldTracking.js +34 -0
- package/dist/ui/Compose/hooks/useActiveFieldTracking.js.map +1 -1
- package/dist/ui/Compose/hooks/useGenerateUpload.js +8 -2
- package/dist/ui/Compose/hooks/useGenerateUpload.js.map +1 -1
- package/dist/ui/ConfigDashboard/index.d.ts +2 -0
- package/dist/ui/ConfigDashboard/index.js +224 -0
- package/dist/ui/ConfigDashboard/index.js.map +1 -0
- package/dist/ui/ConfigDashboard/index.jsx +175 -0
- package/dist/ui/DynamicModelSelect/index.js +1 -1
- package/dist/ui/DynamicModelSelect/index.js.map +1 -1
- package/dist/ui/DynamicModelSelect/index.jsx +1 -1
- package/dist/ui/DynamicProviderSelect/index.js +1 -1
- package/dist/ui/DynamicProviderSelect/index.js.map +1 -1
- package/dist/ui/DynamicProviderSelect/index.jsx +1 -1
- package/dist/ui/DynamicVoiceSelect/index.js +1 -1
- package/dist/ui/DynamicVoiceSelect/index.js.map +1 -1
- package/dist/ui/DynamicVoiceSelect/index.jsx +1 -1
- package/dist/ui/ProviderOptionsEditor/index.js +1 -1
- package/dist/ui/ProviderOptionsEditor/index.js.map +1 -1
- package/dist/ui/ProviderOptionsEditor/index.jsx +1 -1
- package/dist/utilities/updateFieldsConfig.js +1 -1
- package/dist/utilities/updateFieldsConfig.js.map +1 -1
- package/package.json +1 -1
- package/dist/collections/AISettings.d.ts +0 -2
- package/dist/collections/AISettings.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/TranslateMenu.tsx"],"sourcesContent":["import locales from 'locale-codes'\nimport React, { memo, useState } from 'react'\n\nimport { useInstructions } from '../../../../providers/InstructionsProvider/useInstructions.js'\nimport { Item } from './Item.js'\nimport { Translate } from './items.js'\nimport styles from './menu.module.scss'\n\nexport const TranslateMenu = ({ onClick }: { onClick: (data: { locale: string }) => void }) => {\n const [show, setShow] = useState(false)\n\n const { enabledLanguages = [] } = useInstructions()\n\n let filteredLocales = locales.all.filter((a) => {\n return a.tag && a.location\n })\n\n if (enabledLanguages?.length) {\n filteredLocales = filteredLocales.filter((a) => enabledLanguages?.includes(a.tag))\n }\n\n const [languages, setLanguages] = useState(filteredLocales)\n const [inputFocus, setInputFocus] = useState(false)\n\n return (\n <div\n className={styles.menu}\n onMouseLeave={() => {\n if (!inputFocus) {\n setShow(false)\n }\n }}\n >\n <Translate\n isActive={show}\n isMenu\n onClick={() => {\n setShow(!show)\n }}\n />\n <div className={styles.hoverMenu} data-show={show}>\n <div\n className={`${styles.menu} ${styles.subMenu}`}\n style={{\n background: 'var(--popup-bg)',\n // minHeight: '300px',\n }}\n >\n <Item\n onClick={() => {}}\n style={{\n background: 'transparent',\n padding: '0 0 5px 0',\n position: 'sticky',\n top: 0,\n }}\n >\n <input\n className={styles.menuInput}\n onBlur={() => setInputFocus(false)}\n onChange={(event) => {\n const value = event.target.value\n setLanguages(\n filteredLocales.filter((l) => {\n const lowerCaseValue = value.toLowerCase()\n return (\n l.name.toLowerCase().startsWith(lowerCaseValue) ||\n (l.location && l.location.toLowerCase().startsWith(lowerCaseValue)) ||\n l.tag.toLowerCase().startsWith(lowerCaseValue)\n )\n }),\n )\n }}\n onFocus={() => setInputFocus(true)}\n placeholder=\"Search...\"\n />\n </Item>\n {languages.map((locale) => {\n return (\n <Item\n key={locale.tag}\n onClick={() => {\n onClick({ locale: locale.tag })\n }}\n >\n <span className={styles.ellipsis}>{`${locale.location} (${locale.tag})`}</span>\n </Item>\n )\n })}\n </div>\n </div>\n </div>\n )\n}\n\nexport const MemoizedTranslateMenu = memo(TranslateMenu)\n"],"names":["locales","React","memo","useState","useInstructions","Item","Translate","styles","TranslateMenu","onClick","show","setShow","enabledLanguages","filteredLocales","all","filter","a","tag","location","length","includes","languages","setLanguages","inputFocus","setInputFocus","div","className","menu","onMouseLeave","isActive","isMenu","hoverMenu","data-show","subMenu","style","background","padding","position","top","input","menuInput","
|
|
1
|
+
{"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/TranslateMenu.tsx"],"sourcesContent":["import locales from 'locale-codes'\nimport React, { memo, useEffect, useRef, useState } from 'react'\n\nimport { useInstructions } from '../../../../providers/InstructionsProvider/useInstructions.js'\nimport { Item } from './Item.js'\nimport { Translate } from './items.js'\nimport styles from './menu.module.scss'\n\ndeclare global {\n interface Window {\n __AI_MENU_INTERACTIVE?: boolean\n }\n}\n\nexport const TranslateMenu = ({ onClick }: { onClick: (data: { locale: string }) => void }) => {\n const [show, setShow] = useState(false)\n const closeTimeoutRef = useRef<NodeJS.Timeout | null>(null)\n\n const { enabledLanguages = [] } = useInstructions()\n\n let filteredLocales = locales.all.filter((a) => {\n return a.tag && a.location\n })\n\n if (enabledLanguages?.length) {\n filteredLocales = filteredLocales.filter((a) => enabledLanguages?.includes(a.tag))\n }\n\n const [languages, setLanguages] = useState(filteredLocales)\n const [inputFocus, setInputFocus] = useState(false)\n\n useEffect(() => {\n if (!show) {\n if (typeof window !== 'undefined') {\n window.__AI_MENU_INTERACTIVE = false\n }\n }\n return () => {\n if (closeTimeoutRef.current) {\n clearTimeout(closeTimeoutRef.current)\n }\n if (show && typeof window !== 'undefined') {\n window.__AI_MENU_INTERACTIVE = false\n }\n }\n }, [show])\n\n return (\n <div\n className={`${styles.menu} ai-interactive-menu`}\n data-ai-interactive=\"true\"\n onBlur={(e) => {\n // Only clear if focus moves outside the menu container\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n if (typeof window !== 'undefined') window.__AI_MENU_INTERACTIVE = false\n }\n }}\n onFocus={() => {\n if (typeof window !== 'undefined') window.__AI_MENU_INTERACTIVE = true\n }}\n onMouseEnter={() => {\n if (closeTimeoutRef.current) {\n clearTimeout(closeTimeoutRef.current)\n closeTimeoutRef.current = null\n }\n if (typeof window !== 'undefined') window.__AI_MENU_INTERACTIVE = true\n }}\n onMouseLeave={() => {\n if (typeof window !== 'undefined') window.__AI_MENU_INTERACTIVE = false\n if (!inputFocus) {\n closeTimeoutRef.current = setTimeout(() => {\n setShow(false)\n }, 400)\n }\n }}\n >\n <Translate\n isActive={show}\n isMenu\n onClick={() => {\n setShow(!show)\n }}\n />\n <div className={styles.hoverMenu} data-show={show}>\n <div\n className={`${styles.menu} ${styles.subMenu}`}\n style={{\n background: 'var(--popup-bg)',\n // minHeight: '300px',\n }}\n >\n <Item\n onClick={() => {}}\n style={{\n background: 'transparent',\n padding: '0 0 5px 0',\n position: 'sticky',\n top: 0,\n }}\n >\n <input\n className={`${styles.menuInput} ai-interactive-menu`}\n data-ai-interactive=\"true\"\n onBlur={() => setInputFocus(false)}\n onChange={(event) => {\n const value = event.target.value\n setLanguages(\n filteredLocales.filter((l) => {\n const lowerCaseValue = value.toLowerCase()\n return (\n l.name.toLowerCase().startsWith(lowerCaseValue) ||\n (l.location && l.location.toLowerCase().startsWith(lowerCaseValue)) ||\n l.tag.toLowerCase().startsWith(lowerCaseValue)\n )\n }),\n )\n }}\n onFocus={() => setInputFocus(true)}\n placeholder=\"Search...\"\n />\n </Item>\n {languages.map((locale) => {\n return (\n <Item\n className=\"ai-interactive-menu\"\n data-ai-interactive=\"true\"\n key={locale.tag}\n onClick={() => {\n onClick({ locale: locale.tag })\n }}\n >\n <span className={styles.ellipsis}>{`${locale.location} (${locale.tag})`}</span>\n </Item>\n )\n })}\n </div>\n </div>\n </div>\n )\n}\n\nexport const MemoizedTranslateMenu = memo(TranslateMenu)\n"],"names":["locales","React","memo","useEffect","useRef","useState","useInstructions","Item","Translate","styles","TranslateMenu","onClick","show","setShow","closeTimeoutRef","enabledLanguages","filteredLocales","all","filter","a","tag","location","length","includes","languages","setLanguages","inputFocus","setInputFocus","window","__AI_MENU_INTERACTIVE","current","clearTimeout","div","className","menu","data-ai-interactive","onBlur","e","currentTarget","contains","relatedTarget","onFocus","onMouseEnter","onMouseLeave","setTimeout","isActive","isMenu","hoverMenu","data-show","subMenu","style","background","padding","position","top","input","menuInput","onChange","event","value","target","l","lowerCaseValue","toLowerCase","name","startsWith","placeholder","map","locale","span","ellipsis","MemoizedTranslateMenu"],"mappings":"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"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import locales from 'locale-codes';
|
|
2
|
-
import React, { memo, useState } from 'react';
|
|
2
|
+
import React, { memo, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { useInstructions } from '../../../../providers/InstructionsProvider/useInstructions.js';
|
|
4
4
|
import { Item } from './Item.js';
|
|
5
5
|
import { Translate } from './items.js';
|
|
6
6
|
import styles from './menu.module.scss';
|
|
7
7
|
export const TranslateMenu = ({ onClick }) => {
|
|
8
8
|
const [show, setShow] = useState(false);
|
|
9
|
+
const closeTimeoutRef = useRef(null);
|
|
9
10
|
const { enabledLanguages = [] } = useInstructions();
|
|
10
11
|
let filteredLocales = locales.all.filter((a) => {
|
|
11
12
|
return a.tag && a.location;
|
|
@@ -15,9 +16,44 @@ export const TranslateMenu = ({ onClick }) => {
|
|
|
15
16
|
}
|
|
16
17
|
const [languages, setLanguages] = useState(filteredLocales);
|
|
17
18
|
const [inputFocus, setInputFocus] = useState(false);
|
|
18
|
-
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!show) {
|
|
21
|
+
if (typeof window !== 'undefined') {
|
|
22
|
+
window.__AI_MENU_INTERACTIVE = false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return () => {
|
|
26
|
+
if (closeTimeoutRef.current) {
|
|
27
|
+
clearTimeout(closeTimeoutRef.current);
|
|
28
|
+
}
|
|
29
|
+
if (show && typeof window !== 'undefined') {
|
|
30
|
+
window.__AI_MENU_INTERACTIVE = false;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}, [show]);
|
|
34
|
+
return (<div className={`${styles.menu} ai-interactive-menu`} data-ai-interactive="true" onBlur={(e) => {
|
|
35
|
+
// Only clear if focus moves outside the menu container
|
|
36
|
+
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
37
|
+
if (typeof window !== 'undefined')
|
|
38
|
+
window.__AI_MENU_INTERACTIVE = false;
|
|
39
|
+
}
|
|
40
|
+
}} onFocus={() => {
|
|
41
|
+
if (typeof window !== 'undefined')
|
|
42
|
+
window.__AI_MENU_INTERACTIVE = true;
|
|
43
|
+
}} onMouseEnter={() => {
|
|
44
|
+
if (closeTimeoutRef.current) {
|
|
45
|
+
clearTimeout(closeTimeoutRef.current);
|
|
46
|
+
closeTimeoutRef.current = null;
|
|
47
|
+
}
|
|
48
|
+
if (typeof window !== 'undefined')
|
|
49
|
+
window.__AI_MENU_INTERACTIVE = true;
|
|
50
|
+
}} onMouseLeave={() => {
|
|
51
|
+
if (typeof window !== 'undefined')
|
|
52
|
+
window.__AI_MENU_INTERACTIVE = false;
|
|
19
53
|
if (!inputFocus) {
|
|
20
|
-
|
|
54
|
+
closeTimeoutRef.current = setTimeout(() => {
|
|
55
|
+
setShow(false);
|
|
56
|
+
}, 400);
|
|
21
57
|
}
|
|
22
58
|
}}>
|
|
23
59
|
<Translate isActive={show} isMenu onClick={() => {
|
|
@@ -34,7 +70,7 @@ export const TranslateMenu = ({ onClick }) => {
|
|
|
34
70
|
position: 'sticky',
|
|
35
71
|
top: 0,
|
|
36
72
|
}}>
|
|
37
|
-
<input className={styles.menuInput} onBlur={() => setInputFocus(false)} onChange={(event) => {
|
|
73
|
+
<input className={`${styles.menuInput} ai-interactive-menu`} data-ai-interactive="true" onBlur={() => setInputFocus(false)} onChange={(event) => {
|
|
38
74
|
const value = event.target.value;
|
|
39
75
|
setLanguages(filteredLocales.filter((l) => {
|
|
40
76
|
const lowerCaseValue = value.toLowerCase();
|
|
@@ -45,7 +81,7 @@ export const TranslateMenu = ({ onClick }) => {
|
|
|
45
81
|
}} onFocus={() => setInputFocus(true)} placeholder="Search..."/>
|
|
46
82
|
</Item>
|
|
47
83
|
{languages.map((locale) => {
|
|
48
|
-
return (<Item key={locale.tag} onClick={() => {
|
|
84
|
+
return (<Item className="ai-interactive-menu" data-ai-interactive="true" key={locale.tag} onClick={() => {
|
|
49
85
|
onClick({ locale: locale.tag });
|
|
50
86
|
}}>
|
|
51
87
|
<span className={styles.ellipsis}>{`${locale.location} (${locale.tag})`}</span>
|
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
.menu {
|
|
29
|
+
--popup-width: 150px;
|
|
30
|
+
|
|
29
31
|
display: flex;
|
|
30
32
|
gap: 1px;
|
|
31
33
|
flex-direction: column;
|
|
@@ -49,7 +51,7 @@
|
|
|
49
51
|
|
|
50
52
|
.subMenu {
|
|
51
53
|
position: absolute;
|
|
52
|
-
left: calc(var(--popup-width) +
|
|
54
|
+
left: calc(var(--popup-width) + 20px);
|
|
53
55
|
width: calc(var(--popup-width) + 12px);
|
|
54
56
|
top: 10px;
|
|
55
57
|
height: calc(100% - 16px);
|
|
@@ -85,6 +87,7 @@
|
|
|
85
87
|
color: var(--theme-elevation-800);
|
|
86
88
|
border-radius: 0;
|
|
87
89
|
-webkit-appearance: none;
|
|
90
|
+
appearance: none;
|
|
88
91
|
|
|
89
92
|
font-size: inherit !important;
|
|
90
93
|
height: auto !important;
|
|
@@ -15,6 +15,8 @@ const fieldTypeCache = new WeakMap();
|
|
|
15
15
|
let pointerDownThrottleTimer = null;
|
|
16
16
|
let focusDebounceTimer = null;
|
|
17
17
|
let currentContainer = null;
|
|
18
|
+
let lastContainer = null // Track last valid container to restore if needed
|
|
19
|
+
;
|
|
18
20
|
let rafId = null // Track RAF to cancel if needed
|
|
19
21
|
;
|
|
20
22
|
/**
|
|
@@ -106,6 +108,8 @@ let rafId = null // Track RAF to cancel if needed
|
|
|
106
108
|
currentContainer?.classList.remove('ai-plugin-active');
|
|
107
109
|
if (next) {
|
|
108
110
|
next.classList.add('ai-plugin-active');
|
|
111
|
+
lastContainer = next // Update last known valid container
|
|
112
|
+
;
|
|
109
113
|
}
|
|
110
114
|
currentContainer = next;
|
|
111
115
|
};
|
|
@@ -113,6 +117,7 @@ const clearActiveContainer = ()=>{
|
|
|
113
117
|
if (currentContainer) {
|
|
114
118
|
currentContainer.classList.remove('ai-plugin-active');
|
|
115
119
|
currentContainer = null;
|
|
120
|
+
// Note: We do NOT clear lastContainer here, allowing restoration
|
|
116
121
|
}
|
|
117
122
|
// Cancel any pending RAF
|
|
118
123
|
if (rafId !== null) {
|
|
@@ -147,6 +152,23 @@ const isInteractiveElement = (element)=>{
|
|
|
147
152
|
}
|
|
148
153
|
return false;
|
|
149
154
|
};
|
|
155
|
+
// Helper for interactive menu check
|
|
156
|
+
const checkInteractiveMenu = (e)=>{
|
|
157
|
+
// Check global flag first (most reliable for mouse/hover interactions)
|
|
158
|
+
if (typeof window !== 'undefined' && window.__AI_MENU_INTERACTIVE) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
const target = e.target;
|
|
162
|
+
// Check target directly
|
|
163
|
+
if (target && target instanceof Element && (target.classList.contains('ai-interactive-menu') || target.hasAttribute('data-ai-interactive'))) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
// Fallback: Check DOM path (for keyboard or specific events)
|
|
167
|
+
const path = e.composedPath();
|
|
168
|
+
return path.some((el)=>{
|
|
169
|
+
return el instanceof Element && (el.classList.contains('ai-interactive-menu') || el.hasAttribute('data-ai-interactive'));
|
|
170
|
+
});
|
|
171
|
+
};
|
|
150
172
|
/**
|
|
151
173
|
* Handle focus events - only activate if focus is on an interactive element within .field-type
|
|
152
174
|
* Performance: Debounced by 10ms to handle rapid focus changes
|
|
@@ -163,6 +185,14 @@ const isInteractiveElement = (element)=>{
|
|
|
163
185
|
if (!isInteractiveElement(target)) {
|
|
164
186
|
return;
|
|
165
187
|
}
|
|
188
|
+
// Check for interactive menu elements using composedPath for robustness
|
|
189
|
+
if (typeof window !== 'undefined' && window.__AI_MENU_INTERACTIVE || checkInteractiveMenu(e)) {
|
|
190
|
+
// If we lost the active state (e.g. due to pointerDown clearing it), restore it
|
|
191
|
+
if (!currentContainer && lastContainer?.isConnected) {
|
|
192
|
+
setActiveContainer(lastContainer);
|
|
193
|
+
}
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
166
196
|
// Clear any pending debounce
|
|
167
197
|
if (focusDebounceTimer !== null) {
|
|
168
198
|
clearTimeout(focusDebounceTimer);
|
|
@@ -182,6 +212,10 @@ const isInteractiveElement = (element)=>{
|
|
|
182
212
|
if (!(target instanceof HTMLElement)) {
|
|
183
213
|
return;
|
|
184
214
|
}
|
|
215
|
+
// Check for interactive menu elements using composedPath
|
|
216
|
+
if (typeof window !== 'undefined' && window.__AI_MENU_INTERACTIVE || checkInteractiveMenu(e)) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
185
219
|
// Early exit if clicking within current container
|
|
186
220
|
if (currentContainer?.isConnected && currentContainer.contains(target)) {
|
|
187
221
|
return;
|
|
@@ -1 +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\n// Performance optimization: Cache container and field type lookups\nconst containerCache = new WeakMap<HTMLElement, HTMLElement | null>()\nconst fieldTypeCache = new WeakMap<HTMLElement, boolean>()\n\n// Performance optimization: Throttle/debounce timers\nlet pointerDownThrottleTimer: null | number = null\nlet focusDebounceTimer: null | number = null\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 * Performance: Early exit if not in a listbox/option element\n */\nconst findContainerFromReactSelect = (target: HTMLElement): HTMLElement | null => {\n // Early exit if element doesn't have role indicator for React Select\n const role = target.getAttribute('role')\n if (!role || !['listbox', 'option'].includes(role)) {\n return null\n }\n\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 * Performance: Uses WeakMap cache to avoid repeated class list checks\n */\nconst isAllowedFieldType = (container: HTMLElement): boolean => {\n // Check cache first\n if (fieldTypeCache.has(container)) {\n return fieldTypeCache.get(container)!\n }\n\n // Compute and cache result\n const result = ALLOWED_FIELD_TYPES.some(\n (type) =>\n container.classList.contains(type) || container.classList.contains(`field-type-${type}`),\n )\n\n fieldTypeCache.set(container, result)\n return result\n}\n\n/**\n * Resolve the .field-type container for a given event target\n * Only returns containers that match allowed field types\n * Performance: Uses WeakMap cache to avoid repeated DOM traversals\n */\nconst resolveContainerFromTarget = (target: EventTarget | null): HTMLElement | null => {\n if (!(target instanceof HTMLElement)) {\n return null\n }\n\n // Check cache first\n if (containerCache.has(target)) {\n const cached = containerCache.get(target)!\n // Validate cache entry is still in DOM\n if (!cached || cached.isConnected) {\n return cached\n }\n // Invalidate stale cache entry\n containerCache.delete(target)\n }\n\n // Perform lookup\n let container = target.closest<HTMLElement>('.field-type')\n\n // Fall back to React Select logic if needed\n if (!container) {\n container = findContainerFromReactSelect(target)\n }\n\n // Validate field type and cache result\n const result = container && isAllowedFieldType(container) ? container : null\n containerCache.set(target, result)\n\n return result\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 * Performance: Debounced by 10ms to handle rapid focus changes\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 // Clear any pending debounce\n if (focusDebounceTimer !== null) {\n clearTimeout(focusDebounceTimer)\n }\n\n // Debounce to reduce work during rapid focus changes (e.g., fast tabbing)\n focusDebounceTimer = window.setTimeout(() => {\n focusDebounceTimer = null\n const container = resolveContainerFromTarget(target)\n setActiveContainer(container)\n }, 10)\n}\n\n/**\n * Handle pointer/mouse events - only switch when clicking a different .field-type\n * Performance: Early exit for non-field clicks + 50ms throttling\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 // Performance: Quick check before expensive traversal\n // If click is nowhere near a field, just clear active state\n if (!target.closest('.field-type')) {\n if (currentContainer) {\n setActiveContainer(null)\n }\n return\n }\n\n // Throttle to prevent excessive work on rapid clicking\n if (pointerDownThrottleTimer !== null) {\n return\n }\n\n const container = resolveContainerFromTarget(target)\n setActiveContainer(container)\n\n // Set throttle timer for 50ms\n pointerDownThrottleTimer = window.setTimeout(() => {\n pointerDownThrottleTimer = null\n }, 50)\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","containerCache","WeakMap","fieldTypeCache","pointerDownThrottleTimer","focusDebounceTimer","currentContainer","rafId","cssEscape","value","CSS","escape","replace","findContainerFromReactSelect","target","role","getAttribute","includes","listbox","closest","id","selector","control","document","querySelector","isAllowedFieldType","container","has","get","result","some","type","classList","contains","set","resolveContainerFromTarget","HTMLElement","cached","isConnected","delete","setActiveContainer","next","remove","add","clearActiveContainer","cancelAnimationFrame","isInteractiveElement","element","tagName","toLowerCase","interactiveTags","isContentEditable","onFocusIn","e","clearTimeout","window","setTimeout","onPointerDown","onKeyDown","key","requestAnimationFrame","activeElement","onVisibilityChange","hidden","useActiveFieldTracking","pluginWindow","__aiComposeTrackingCount","__aiComposeTracking","controller","AbortController","__aiComposeTrackingController","addEventListener","capture","signal","passive","abort","undefined"],"mappings":"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"}
|
|
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\n// Performance optimization: Cache container and field type lookups\nconst containerCache = new WeakMap<HTMLElement, HTMLElement | null>()\nconst fieldTypeCache = new WeakMap<HTMLElement, boolean>()\n\n// Performance optimization: Throttle/debounce timers\nlet pointerDownThrottleTimer: null | number = null\nlet focusDebounceTimer: null | number = null\n\nlet currentContainer: HTMLElement | null = null\nlet lastContainer: HTMLElement | null = null // Track last valid container to restore if needed\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 * Performance: Early exit if not in a listbox/option element\n */\nconst findContainerFromReactSelect = (target: HTMLElement): HTMLElement | null => {\n // Early exit if element doesn't have role indicator for React Select\n const role = target.getAttribute('role')\n if (!role || !['listbox', 'option'].includes(role)) {\n return null\n }\n\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 * Performance: Uses WeakMap cache to avoid repeated class list checks\n */\nconst isAllowedFieldType = (container: HTMLElement): boolean => {\n // Check cache first\n if (fieldTypeCache.has(container)) {\n return fieldTypeCache.get(container)!\n }\n\n // Compute and cache result\n const result = ALLOWED_FIELD_TYPES.some(\n (type) =>\n container.classList.contains(type) || container.classList.contains(`field-type-${type}`),\n )\n\n fieldTypeCache.set(container, result)\n return result\n}\n\n/**\n * Resolve the .field-type container for a given event target\n * Only returns containers that match allowed field types\n * Performance: Uses WeakMap cache to avoid repeated DOM traversals\n */\nconst resolveContainerFromTarget = (target: EventTarget | null): HTMLElement | null => {\n if (!(target instanceof HTMLElement)) {\n return null\n }\n\n // Check cache first\n if (containerCache.has(target)) {\n const cached = containerCache.get(target)!\n // Validate cache entry is still in DOM\n if (!cached || cached.isConnected) {\n return cached\n }\n // Invalidate stale cache entry\n containerCache.delete(target)\n }\n\n // Perform lookup\n let container = target.closest<HTMLElement>('.field-type')\n\n // Fall back to React Select logic if needed\n if (!container) {\n container = findContainerFromReactSelect(target)\n }\n\n // Validate field type and cache result\n const result = container && isAllowedFieldType(container) ? container : null\n containerCache.set(target, result)\n\n return result\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 lastContainer = next // Update last known valid container\n }\n currentContainer = next\n}\n\nconst clearActiveContainer = (): void => {\n if (currentContainer) {\n currentContainer.classList.remove('ai-plugin-active')\n currentContainer = null\n // Note: We do NOT clear lastContainer here, allowing restoration\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// Helper for interactive menu check\nconst checkInteractiveMenu = (e: Event): boolean => {\n // Check global flag first (most reliable for mouse/hover interactions)\n if (typeof window !== 'undefined' && window.__AI_MENU_INTERACTIVE) {\n return true\n }\n\n const target = e.target as Element\n\n // Check target directly\n if (\n target &&\n target instanceof Element &&\n (target.classList.contains('ai-interactive-menu') || target.hasAttribute('data-ai-interactive'))\n ) {\n return true\n }\n\n // Fallback: Check DOM path (for keyboard or specific events)\n const path = e.composedPath()\n return path.some((el) => {\n return (\n el instanceof Element &&\n (el.classList.contains('ai-interactive-menu') || el.hasAttribute('data-ai-interactive'))\n )\n })\n}\n\n/**\n * Handle focus events - only activate if focus is on an interactive element within .field-type\n * Performance: Debounced by 10ms to handle rapid focus changes\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 // Check for interactive menu elements using composedPath for robustness\n if (typeof window !== 'undefined' && window.__AI_MENU_INTERACTIVE || checkInteractiveMenu(e)) {\n // If we lost the active state (e.g. due to pointerDown clearing it), restore it\n if (!currentContainer && lastContainer?.isConnected) {\n setActiveContainer(lastContainer)\n }\n return\n }\n\n // Clear any pending debounce\n if (focusDebounceTimer !== null) {\n clearTimeout(focusDebounceTimer)\n }\n\n // Debounce to reduce work during rapid focus changes (e.g., fast tabbing)\n focusDebounceTimer = window.setTimeout(() => {\n focusDebounceTimer = null\n const container = resolveContainerFromTarget(target)\n setActiveContainer(container)\n }, 10)\n}\n\n/**\n * Handle pointer/mouse events - only switch when clicking a different .field-type\n * Performance: Early exit for non-field clicks + 50ms throttling\n */\nconst onPointerDown = (e: PointerEvent): void => {\n const target = e.target\n if (!(target instanceof HTMLElement)) {\n return\n }\n\n // Check for interactive menu elements using composedPath\n if (typeof window !== 'undefined' && window.__AI_MENU_INTERACTIVE || checkInteractiveMenu(e)) {\n return\n }\n\n // Early exit if clicking within current container\n if (currentContainer?.isConnected && currentContainer.contains(target)) {\n return\n }\n\n // Performance: Quick check before expensive traversal\n // If click is nowhere near a field, just clear active state\n if (!target.closest('.field-type')) {\n if (currentContainer) {\n setActiveContainer(null)\n }\n return\n }\n\n // Throttle to prevent excessive work on rapid clicking\n if (pointerDownThrottleTimer !== null) {\n return\n }\n\n const container = resolveContainerFromTarget(target)\n setActiveContainer(container)\n\n // Set throttle timer for 50ms\n pointerDownThrottleTimer = window.setTimeout(() => {\n pointerDownThrottleTimer = null\n }, 50)\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","containerCache","WeakMap","fieldTypeCache","pointerDownThrottleTimer","focusDebounceTimer","currentContainer","lastContainer","rafId","cssEscape","value","CSS","escape","replace","findContainerFromReactSelect","target","role","getAttribute","includes","listbox","closest","id","selector","control","document","querySelector","isAllowedFieldType","container","has","get","result","some","type","classList","contains","set","resolveContainerFromTarget","HTMLElement","cached","isConnected","delete","setActiveContainer","next","remove","add","clearActiveContainer","cancelAnimationFrame","isInteractiveElement","element","tagName","toLowerCase","interactiveTags","isContentEditable","checkInteractiveMenu","e","window","__AI_MENU_INTERACTIVE","Element","hasAttribute","path","composedPath","el","onFocusIn","clearTimeout","setTimeout","onPointerDown","onKeyDown","key","requestAnimationFrame","activeElement","onVisibilityChange","hidden","useActiveFieldTracking","pluginWindow","__aiComposeTrackingCount","__aiComposeTracking","controller","AbortController","__aiComposeTrackingController","addEventListener","capture","signal","passive","abort","undefined"],"mappings":"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"}
|
|
@@ -41,8 +41,14 @@ export const useGenerateUpload = ({ instructionIdRef })=>{
|
|
|
41
41
|
const json = await uploadResponse.json();
|
|
42
42
|
const { job, result } = json || {};
|
|
43
43
|
if (result) {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
if (Array.isArray(result)) {
|
|
45
|
+
const ids = result.map((r)=>r.id);
|
|
46
|
+
setValue(ids);
|
|
47
|
+
setHistory(ids);
|
|
48
|
+
} else {
|
|
49
|
+
setValue(result?.id);
|
|
50
|
+
setHistory(result?.id);
|
|
51
|
+
}
|
|
46
52
|
// Show toast to prompt user to save
|
|
47
53
|
toast.success('Image generated successfully! Click Save to see the preview.');
|
|
48
54
|
return uploadResponse;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/Compose/hooks/useGenerateUpload.ts"],"sourcesContent":["import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui'\nimport { type RefObject, useCallback, useState } from 'react'\n\nimport type { GenerateTextarea } from '../../../types.js'\n\nimport { PLUGIN_AI_JOBS_TABLE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD } from '../../../defaults.js'\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\nimport { useHistory } from './useHistory.js'\n\ntype UseGenerateUploadParams = {\n instructionIdRef: RefObject<string>\n}\n\nexport const useGenerateUpload = ({ instructionIdRef }: UseGenerateUploadParams) => {\n const { config } = useConfig()\n const {\n routes: { api },\n serverURL,\n } = config\n const { id: documentId, collectionSlug } = useDocumentInfo()\n const localFromContext = useLocale()\n const { getData } = useForm()\n const { set: setHistory } = useHistory()\n\n const { field, path: pathFromContext } = useFieldProps()\n const { setValue } = useField<any>({\n path: pathFromContext ?? '',\n })\n\n // Async job UI state\n const [jobStatus, setJobStatus] = useState<string | undefined>(undefined)\n const [jobProgress, setJobProgress] = useState<number>(0)\n const [isJobActive, setIsJobActive] = useState<boolean>(false)\n\n const generateUpload = useCallback(async () => {\n const doc = getData()\n const currentInstructionId = instructionIdRef.current\n\n return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {\n body: JSON.stringify({\n collectionSlug: collectionSlug ?? '',\n doc,\n documentId,\n locale: localFromContext?.code,\n options: {\n instructionId: currentInstructionId,\n },\n } satisfies Parameters<GenerateTextarea>[0]),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n .then(async (uploadResponse) => {\n if (uploadResponse.ok) {\n const json = await uploadResponse.json()\n const { job, result } = json || {}\n if (result) {\n setValue(result?.id)\n setHistory(result?.id)\n\n // Show toast to prompt user to save\n toast.success('Image generated successfully! Click Save to see the preview.')\n\n return uploadResponse\n }\n\n // Async job: poll AI Jobs collection for status/progress/result_id\n if (job && job.id) {\n setIsJobActive(true)\n const cancelled = false\n let attempts = 0\n const maxAttempts = 600 // up to ~10 minutes @ 1s\n\n // Basic in-hook state via closure variables; UI will re-render off fetches below\n const poll = async (): Promise<void> => {\n if (cancelled) {\n return\n }\n try {\n const res = await fetch(`${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`, {\n credentials: 'include',\n })\n if (res.ok) {\n const jobDoc = await res.json()\n const { progress, result_id, status } = jobDoc || {}\n setJobStatus(status)\n setJobProgress(progress ?? 0)\n // When result present, set field and finish\n if (status === 'completed' && result_id) {\n let valueToSet = result_id\n\n // Attempt to fetch full document for immediate preview\n if (field && 'relationTo' in field && typeof field.relationTo === 'string') {\n let attempts = 0\n const maxAttempts = 3\n while (attempts < maxAttempts) {\n try {\n const docRes = await fetch(\n `${serverURL}${api}/${field.relationTo}/${result_id}`,\n {\n credentials: 'include',\n },\n )\n if (docRes.ok) {\n const doc = await docRes.json()\n // Verify we have a URL for preview\n if (doc && doc.url) {\n valueToSet = doc\n break\n }\n }\n } catch (e) {\n console.error('Failed to fetch generated document for preview:', e)\n }\n attempts++\n if (attempts < maxAttempts) {\n await new Promise((resolve) => setTimeout(resolve, 500))\n }\n }\n }\n\n setValue(valueToSet)\n setHistory(result_id)\n setIsJobActive(false)\n return\n }\n if (status === 'failed') {\n setIsJobActive(false)\n throw new Error('Video generation failed')\n }\n }\n } catch (_) {\n // silent retry\n }\n\n attempts += 1\n if (!cancelled && attempts < maxAttempts) {\n setTimeout(poll, 1000)\n }\n }\n setTimeout(poll, 1000)\n return uploadResponse\n }\n\n throw new Error('generateUpload: Unexpected response')\n } else {\n const { errors = [] } = await uploadResponse.json()\n const errStr = errors.map((error: any) => error.message).join(', ')\n throw new Error(errStr)\n }\n })\n .catch((error) => {\n toast.error(`Failed to generate: ${error.message}`)\n console.error(\n 'Error generating or setting your upload, please set it manually if its saved in your media files.',\n error,\n )\n })\n }, [\n getData,\n localFromContext?.code,\n instructionIdRef,\n // setValue,\n documentId,\n collectionSlug,\n serverURL,\n api,\n setHistory,\n ])\n\n return {\n generateUpload,\n isJobActive,\n jobProgress,\n jobStatus,\n }\n}\n"],"names":["toast","useConfig","useDocumentInfo","useField","useForm","useLocale","useCallback","useState","PLUGIN_AI_JOBS_TABLE","PLUGIN_API_ENDPOINT_GENERATE_UPLOAD","useFieldProps","useHistory","useGenerateUpload","instructionIdRef","config","routes","api","serverURL","id","documentId","collectionSlug","localFromContext","getData","set","setHistory","field","path","pathFromContext","setValue","jobStatus","setJobStatus","undefined","jobProgress","setJobProgress","isJobActive","setIsJobActive","generateUpload","doc","currentInstructionId","current","fetch","body","JSON","stringify","locale","code","options","instructionId","credentials","headers","method","then","uploadResponse","ok","json","job","result","success","cancelled","attempts","maxAttempts","poll","res","jobDoc","progress","result_id","status","valueToSet","relationTo","docRes","url","e","console","error","Promise","resolve","setTimeout","Error","_","errors","errStr","map","message","join","catch"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/Compose/hooks/useGenerateUpload.ts"],"sourcesContent":["import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui'\nimport { type RefObject, useCallback, useState } from 'react'\n\nimport type { GenerateTextarea } from '../../../types.js'\n\nimport { PLUGIN_AI_JOBS_TABLE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD } from '../../../defaults.js'\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\nimport { useHistory } from './useHistory.js'\n\ntype UseGenerateUploadParams = {\n instructionIdRef: RefObject<string>\n}\n\nexport const useGenerateUpload = ({ instructionIdRef }: UseGenerateUploadParams) => {\n const { config } = useConfig()\n const {\n routes: { api },\n serverURL,\n } = config\n const { id: documentId, collectionSlug } = useDocumentInfo()\n const localFromContext = useLocale()\n const { getData } = useForm()\n const { set: setHistory } = useHistory()\n\n const { field, path: pathFromContext } = useFieldProps()\n const { setValue } = useField<any>({\n path: pathFromContext ?? '',\n })\n\n // Async job UI state\n const [jobStatus, setJobStatus] = useState<string | undefined>(undefined)\n const [jobProgress, setJobProgress] = useState<number>(0)\n const [isJobActive, setIsJobActive] = useState<boolean>(false)\n\n const generateUpload = useCallback(async () => {\n const doc = getData()\n const currentInstructionId = instructionIdRef.current\n\n return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {\n body: JSON.stringify({\n collectionSlug: collectionSlug ?? '',\n doc,\n documentId,\n locale: localFromContext?.code,\n options: {\n instructionId: currentInstructionId,\n },\n } satisfies Parameters<GenerateTextarea>[0]),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n .then(async (uploadResponse) => {\n if (uploadResponse.ok) {\n const json = await uploadResponse.json()\n const { job, result } = json || {}\n if (result) {\n if (Array.isArray(result)) {\n const ids = result.map((r: any) => r.id)\n setValue(ids)\n setHistory(ids)\n } else {\n setValue(result?.id)\n setHistory(result?.id)\n }\n\n // Show toast to prompt user to save\n toast.success('Image generated successfully! Click Save to see the preview.')\n\n return uploadResponse\n }\n\n // Async job: poll AI Jobs collection for status/progress/result_id\n if (job && job.id) {\n setIsJobActive(true)\n const cancelled = false\n let attempts = 0\n const maxAttempts = 600 // up to ~10 minutes @ 1s\n\n // Basic in-hook state via closure variables; UI will re-render off fetches below\n const poll = async (): Promise<void> => {\n if (cancelled) {\n return\n }\n try {\n const res = await fetch(`${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`, {\n credentials: 'include',\n })\n if (res.ok) {\n const jobDoc = await res.json()\n const { progress, result_id, status } = jobDoc || {}\n setJobStatus(status)\n setJobProgress(progress ?? 0)\n // When result present, set field and finish\n if (status === 'completed' && result_id) {\n let valueToSet = result_id\n\n // Attempt to fetch full document for immediate preview\n if (field && 'relationTo' in field && typeof field.relationTo === 'string') {\n let attempts = 0\n const maxAttempts = 3\n while (attempts < maxAttempts) {\n try {\n const docRes = await fetch(\n `${serverURL}${api}/${field.relationTo}/${result_id}`,\n {\n credentials: 'include',\n },\n )\n if (docRes.ok) {\n const doc = await docRes.json()\n // Verify we have a URL for preview\n if (doc && doc.url) {\n valueToSet = doc\n break\n }\n }\n } catch (e) {\n console.error('Failed to fetch generated document for preview:', e)\n }\n attempts++\n if (attempts < maxAttempts) {\n await new Promise((resolve) => setTimeout(resolve, 500))\n }\n }\n }\n\n setValue(valueToSet)\n setHistory(result_id)\n setIsJobActive(false)\n return\n }\n if (status === 'failed') {\n setIsJobActive(false)\n throw new Error('Video generation failed')\n }\n }\n } catch (_) {\n // silent retry\n }\n\n attempts += 1\n if (!cancelled && attempts < maxAttempts) {\n setTimeout(poll, 1000)\n }\n }\n setTimeout(poll, 1000)\n return uploadResponse\n }\n\n throw new Error('generateUpload: Unexpected response')\n } else {\n const { errors = [] } = await uploadResponse.json()\n const errStr = errors.map((error: any) => error.message).join(', ')\n throw new Error(errStr)\n }\n })\n .catch((error) => {\n toast.error(`Failed to generate: ${error.message}`)\n console.error(\n 'Error generating or setting your upload, please set it manually if its saved in your media files.',\n error,\n )\n })\n }, [\n getData,\n localFromContext?.code,\n instructionIdRef,\n // setValue,\n documentId,\n collectionSlug,\n serverURL,\n api,\n setHistory,\n ])\n\n return {\n generateUpload,\n isJobActive,\n jobProgress,\n jobStatus,\n }\n}\n"],"names":["toast","useConfig","useDocumentInfo","useField","useForm","useLocale","useCallback","useState","PLUGIN_AI_JOBS_TABLE","PLUGIN_API_ENDPOINT_GENERATE_UPLOAD","useFieldProps","useHistory","useGenerateUpload","instructionIdRef","config","routes","api","serverURL","id","documentId","collectionSlug","localFromContext","getData","set","setHistory","field","path","pathFromContext","setValue","jobStatus","setJobStatus","undefined","jobProgress","setJobProgress","isJobActive","setIsJobActive","generateUpload","doc","currentInstructionId","current","fetch","body","JSON","stringify","locale","code","options","instructionId","credentials","headers","method","then","uploadResponse","ok","json","job","result","Array","isArray","ids","map","r","success","cancelled","attempts","maxAttempts","poll","res","jobDoc","progress","result_id","status","valueToSet","relationTo","docRes","url","e","console","error","Promise","resolve","setTimeout","Error","_","errors","errStr","message","join","catch"],"mappings":"AAAA,SAASA,KAAK,EAAEC,SAAS,EAAEC,eAAe,EAAEC,QAAQ,EAAEC,OAAO,EAAEC,SAAS,QAAQ,iBAAgB;AAChG,SAAyBC,WAAW,EAAEC,QAAQ,QAAQ,QAAO;AAI7D,SAASC,oBAAoB,EAAEC,mCAAmC,QAAQ,uBAAsB;AAChG,SAASC,aAAa,QAAQ,oDAAmD;AACjF,SAASC,UAAU,QAAQ,kBAAiB;AAM5C,OAAO,MAAMC,oBAAoB,CAAC,EAAEC,gBAAgB,EAA2B;IAC7E,MAAM,EAAEC,MAAM,EAAE,GAAGb;IACnB,MAAM,EACJc,QAAQ,EAAEC,GAAG,EAAE,EACfC,SAAS,EACV,GAAGH;IACJ,MAAM,EAAEI,IAAIC,UAAU,EAAEC,cAAc,EAAE,GAAGlB;IAC3C,MAAMmB,mBAAmBhB;IACzB,MAAM,EAAEiB,OAAO,EAAE,GAAGlB;IACpB,MAAM,EAAEmB,KAAKC,UAAU,EAAE,GAAGb;IAE5B,MAAM,EAAEc,KAAK,EAAEC,MAAMC,eAAe,EAAE,GAAGjB;IACzC,MAAM,EAAEkB,QAAQ,EAAE,GAAGzB,SAAc;QACjCuB,MAAMC,mBAAmB;IAC3B;IAEA,qBAAqB;IACrB,MAAM,CAACE,WAAWC,aAAa,GAAGvB,SAA6BwB;IAC/D,MAAM,CAACC,aAAaC,eAAe,GAAG1B,SAAiB;IACvD,MAAM,CAAC2B,aAAaC,eAAe,GAAG5B,SAAkB;IAExD,MAAM6B,iBAAiB9B,YAAY;QACjC,MAAM+B,MAAMf;QACZ,MAAMgB,uBAAuBzB,iBAAiB0B,OAAO;QAErD,OAAOC,MAAM,CAAC,EAAEvB,UAAU,EAAED,IAAI,EAAEP,oCAAoC,CAAC,EAAE;YACvEgC,MAAMC,KAAKC,SAAS,CAAC;gBACnBvB,gBAAgBA,kBAAkB;gBAClCiB;gBACAlB;gBACAyB,QAAQvB,kBAAkBwB;gBAC1BC,SAAS;oBACPC,eAAeT;gBACjB;YACF;YACAU,aAAa;YACbC,SAAS;gBACP,gBAAgB;YAClB;YACAC,QAAQ;QACV,GACGC,IAAI,CAAC,OAAOC;YACX,IAAIA,eAAeC,EAAE,EAAE;gBACrB,MAAMC,OAAO,MAAMF,eAAeE,IAAI;gBACtC,MAAM,EAAEC,GAAG,EAAEC,MAAM,EAAE,GAAGF,QAAQ,CAAC;gBACjC,IAAIE,QAAQ;oBACV,IAAIC,MAAMC,OAAO,CAACF,SAAS;wBACzB,MAAMG,MAAMH,OAAOI,GAAG,CAAC,CAACC,IAAWA,EAAE3C,EAAE;wBACvCU,SAAS+B;wBACTnC,WAAWmC;oBACb,OAAO;wBACL/B,SAAS4B,QAAQtC;wBACjBM,WAAWgC,QAAQtC;oBACrB;oBAEA,oCAAoC;oBACpClB,MAAM8D,OAAO,CAAC;oBAEd,OAAOV;gBACT;gBAEA,mEAAmE;gBACnE,IAAIG,OAAOA,IAAIrC,EAAE,EAAE;oBACjBiB,eAAe;oBACf,MAAM4B,YAAY;oBAClB,IAAIC,WAAW;oBACf,MAAMC,cAAc,IAAI,yBAAyB;;oBAEjD,iFAAiF;oBACjF,MAAMC,OAAO;wBACX,IAAIH,WAAW;4BACb;wBACF;wBACA,IAAI;4BACF,MAAMI,MAAM,MAAM3B,MAAM,CAAC,EAAEvB,UAAU,EAAED,IAAI,CAAC,EAAER,qBAAqB,CAAC,EAAE+C,IAAIrC,EAAE,CAAC,CAAC,EAAE;gCAC9E8B,aAAa;4BACf;4BACA,IAAImB,IAAId,EAAE,EAAE;gCACV,MAAMe,SAAS,MAAMD,IAAIb,IAAI;gCAC7B,MAAM,EAAEe,QAAQ,EAAEC,SAAS,EAAEC,MAAM,EAAE,GAAGH,UAAU,CAAC;gCACnDtC,aAAayC;gCACbtC,eAAeoC,YAAY;gCAC3B,4CAA4C;gCAC5C,IAAIE,WAAW,eAAeD,WAAW;oCACvC,IAAIE,aAAaF;oCAEjB,uDAAuD;oCACvD,IAAI7C,SAAS,gBAAgBA,SAAS,OAAOA,MAAMgD,UAAU,KAAK,UAAU;wCAC1E,IAAIT,WAAW;wCACf,MAAMC,cAAc;wCACpB,MAAOD,WAAWC,YAAa;4CAC7B,IAAI;gDACF,MAAMS,SAAS,MAAMlC,MACnB,CAAC,EAAEvB,UAAU,EAAED,IAAI,CAAC,EAAES,MAAMgD,UAAU,CAAC,CAAC,EAAEH,UAAU,CAAC,EACrD;oDACEtB,aAAa;gDACf;gDAEF,IAAI0B,OAAOrB,EAAE,EAAE;oDACb,MAAMhB,MAAM,MAAMqC,OAAOpB,IAAI;oDAC7B,mCAAmC;oDACnC,IAAIjB,OAAOA,IAAIsC,GAAG,EAAE;wDAClBH,aAAanC;wDACb;oDACF;gDACF;4CACF,EAAE,OAAOuC,GAAG;gDACVC,QAAQC,KAAK,CAAC,mDAAmDF;4CACnE;4CACAZ;4CACA,IAAIA,WAAWC,aAAa;gDAC1B,MAAM,IAAIc,QAAQ,CAACC,UAAYC,WAAWD,SAAS;4CACrD;wCACF;oCACF;oCAEApD,SAAS4C;oCACThD,WAAW8C;oCACXnC,eAAe;oCACf;gCACF;gCACA,IAAIoC,WAAW,UAAU;oCACvBpC,eAAe;oCACf,MAAM,IAAI+C,MAAM;gCAClB;4BACF;wBACF,EAAE,OAAOC,GAAG;wBACV,eAAe;wBACjB;wBAEAnB,YAAY;wBACZ,IAAI,CAACD,aAAaC,WAAWC,aAAa;4BACxCgB,WAAWf,MAAM;wBACnB;oBACF;oBACAe,WAAWf,MAAM;oBACjB,OAAOd;gBACT;gBAEA,MAAM,IAAI8B,MAAM;YAClB,OAAO;gBACL,MAAM,EAAEE,SAAS,EAAE,EAAE,GAAG,MAAMhC,eAAeE,IAAI;gBACjD,MAAM+B,SAASD,OAAOxB,GAAG,CAAC,CAACkB,QAAeA,MAAMQ,OAAO,EAAEC,IAAI,CAAC;gBAC9D,MAAM,IAAIL,MAAMG;YAClB;QACF,GACCG,KAAK,CAAC,CAACV;YACN9E,MAAM8E,KAAK,CAAC,CAAC,oBAAoB,EAAEA,MAAMQ,OAAO,CAAC,CAAC;YAClDT,QAAQC,KAAK,CACX,qGACAA;QAEJ;IACJ,GAAG;QACDxD;QACAD,kBAAkBwB;QAClBhC;QACA,YAAY;QACZM;QACAC;QACAH;QACAD;QACAQ;KACD;IAED,OAAO;QACLY;QACAF;QACAF;QACAH;IACF;AACF,EAAC"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button, toast, useConfig } from '@payloadcms/ui';
|
|
4
|
+
// @ts-expect-error - Next.js types are not resolving correctly with nodenext but runtime is fine
|
|
5
|
+
import { useRouter } from 'next/navigation';
|
|
6
|
+
import React, { use, useEffect, useState } from 'react';
|
|
7
|
+
import { excludeCollections } from '../../defaults.js';
|
|
8
|
+
import { InstructionsContext } from '../../providers/InstructionsProvider/context.js';
|
|
9
|
+
export const ConfigDashboard = ()=>{
|
|
10
|
+
const { config: { collections, routes: { admin: adminRoute, api: apiRoute } } } = useConfig();
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const { refresh, setEnabledCollections: setEnabledCollectionsInContext } = use(InstructionsContext);
|
|
13
|
+
const [enabledCollections, setEnabledCollections] = useState([]);
|
|
14
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
15
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
16
|
+
const availableCollections = collections.filter((c)=>!excludeCollections.includes(c.slug) && !c.admin?.hidden);
|
|
17
|
+
useEffect(()=>{
|
|
18
|
+
const fetchSettings = async ()=>{
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(`${apiRoute}/globals/ai-providers`);
|
|
21
|
+
if (response.ok) {
|
|
22
|
+
const data = await response.json();
|
|
23
|
+
// Handle both simple array and object wrapper if Payload wraps it
|
|
24
|
+
const storedEnabled = data.enabledCollections || [];
|
|
25
|
+
setEnabledCollections(Array.isArray(storedEnabled) ? storedEnabled : []);
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Failed to fetch AI settings:', error);
|
|
29
|
+
} finally{
|
|
30
|
+
setIsLoading(false);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
fetchSettings().catch((e)=>{
|
|
34
|
+
console.log(e);
|
|
35
|
+
});
|
|
36
|
+
}, [
|
|
37
|
+
apiRoute
|
|
38
|
+
]);
|
|
39
|
+
const handleToggle = (slug)=>{
|
|
40
|
+
setEnabledCollections((prev)=>{
|
|
41
|
+
if (prev.includes(slug)) {
|
|
42
|
+
return prev.filter((s)=>s !== slug);
|
|
43
|
+
}
|
|
44
|
+
return [
|
|
45
|
+
...prev,
|
|
46
|
+
slug
|
|
47
|
+
];
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const handleSave = async ()=>{
|
|
51
|
+
setIsSaving(true);
|
|
52
|
+
try {
|
|
53
|
+
// First fetch current settings to get ID or just rely on global update behavior
|
|
54
|
+
// We need to adhere to Payload's global update API
|
|
55
|
+
const response = await fetch(`${apiRoute}/globals/ai-providers`, {
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
enabledCollections
|
|
58
|
+
}),
|
|
59
|
+
headers: {
|
|
60
|
+
'Content-Type': 'application/json'
|
|
61
|
+
},
|
|
62
|
+
method: 'POST'
|
|
63
|
+
});
|
|
64
|
+
if (response.ok) {
|
|
65
|
+
toast.success('Settings saved successfully');
|
|
66
|
+
if (setEnabledCollectionsInContext) {
|
|
67
|
+
setEnabledCollectionsInContext(enabledCollections);
|
|
68
|
+
}
|
|
69
|
+
if (refresh) {
|
|
70
|
+
await refresh();
|
|
71
|
+
}
|
|
72
|
+
router.refresh();
|
|
73
|
+
} else {
|
|
74
|
+
toast.error('Failed to save settings');
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('Error saving settings:', error);
|
|
78
|
+
toast.error('Error saving settings');
|
|
79
|
+
} finally{
|
|
80
|
+
setIsSaving(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
if (isLoading) {
|
|
84
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
85
|
+
style: {
|
|
86
|
+
padding: '20px',
|
|
87
|
+
textAlign: 'center'
|
|
88
|
+
},
|
|
89
|
+
children: "Loading configuration..."
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
93
|
+
style: {
|
|
94
|
+
background: 'var(--theme-elevation-50)',
|
|
95
|
+
// border: '1px solid var(--theme-elevation-150)',
|
|
96
|
+
// borderRadius: '8px',
|
|
97
|
+
// borderBottom: '1px solid var(--theme-elevation-150)',
|
|
98
|
+
// borderTop: '1px solid var(--theme-elevation-150)',
|
|
99
|
+
marginBottom: '20px',
|
|
100
|
+
overflow: 'hidden'
|
|
101
|
+
},
|
|
102
|
+
children: [
|
|
103
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
104
|
+
style: {
|
|
105
|
+
alignItems: 'center',
|
|
106
|
+
borderBottom: '1px solid var(--theme-elevation-150)',
|
|
107
|
+
display: 'flex',
|
|
108
|
+
justifyContent: 'space-between',
|
|
109
|
+
padding: '8px var(--gutter-h)'
|
|
110
|
+
},
|
|
111
|
+
children: [
|
|
112
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
113
|
+
children: [
|
|
114
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
115
|
+
style: {
|
|
116
|
+
margin: '0 0 5px 0'
|
|
117
|
+
},
|
|
118
|
+
children: "Let's configure your AI Plugin"
|
|
119
|
+
}),
|
|
120
|
+
/*#__PURE__*/ _jsx("p", {
|
|
121
|
+
style: {
|
|
122
|
+
color: 'var(--theme-elevation-500)',
|
|
123
|
+
fontSize: '14px',
|
|
124
|
+
margin: '0'
|
|
125
|
+
},
|
|
126
|
+
children: "Set up the provider → Choose the content → Refine the behavior."
|
|
127
|
+
})
|
|
128
|
+
]
|
|
129
|
+
}),
|
|
130
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
131
|
+
style: {
|
|
132
|
+
display: 'flex',
|
|
133
|
+
gap: '10px'
|
|
134
|
+
},
|
|
135
|
+
children: [
|
|
136
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
137
|
+
buttonStyle: "secondary",
|
|
138
|
+
el: "link",
|
|
139
|
+
to: `${adminRoute}/globals/ai-providers`,
|
|
140
|
+
children: "Providers"
|
|
141
|
+
}),
|
|
142
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
143
|
+
disabled: isSaving,
|
|
144
|
+
onClick: handleSave,
|
|
145
|
+
children: isSaving ? 'Saving...' : 'Save Changes'
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
})
|
|
149
|
+
]
|
|
150
|
+
}),
|
|
151
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
152
|
+
style: {
|
|
153
|
+
padding: '24px var(--gutter-h)'
|
|
154
|
+
},
|
|
155
|
+
children: [
|
|
156
|
+
/*#__PURE__*/ _jsx("h5", {
|
|
157
|
+
style: {
|
|
158
|
+
marginBottom: '15px'
|
|
159
|
+
},
|
|
160
|
+
children: "Select the collections where AI features should be available, toggle them on or off, and save your changes."
|
|
161
|
+
}),
|
|
162
|
+
/*#__PURE__*/ _jsx("div", {
|
|
163
|
+
style: {
|
|
164
|
+
display: 'grid',
|
|
165
|
+
gap: '15px',
|
|
166
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))'
|
|
167
|
+
},
|
|
168
|
+
children: availableCollections.map((collection)=>{
|
|
169
|
+
const isEnabled = enabledCollections.includes(collection.slug);
|
|
170
|
+
return /*#__PURE__*/ _jsxs("button", {
|
|
171
|
+
onClick: ()=>handleToggle(collection.slug),
|
|
172
|
+
style: {
|
|
173
|
+
alignItems: 'center',
|
|
174
|
+
background: isEnabled ? 'var(--theme-elevation-100)' : 'var(--theme-elevation-50)',
|
|
175
|
+
border: `1px solid ${isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)'}`,
|
|
176
|
+
borderRadius: '6px',
|
|
177
|
+
cursor: 'pointer',
|
|
178
|
+
display: 'flex',
|
|
179
|
+
gap: '10px',
|
|
180
|
+
padding: '10px 15px',
|
|
181
|
+
textAlign: 'left',
|
|
182
|
+
transition: 'all 0.2s ease',
|
|
183
|
+
width: '100%'
|
|
184
|
+
},
|
|
185
|
+
type: "button",
|
|
186
|
+
children: [
|
|
187
|
+
/*#__PURE__*/ _jsx("div", {
|
|
188
|
+
style: {
|
|
189
|
+
alignItems: 'center',
|
|
190
|
+
background: isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)',
|
|
191
|
+
borderRadius: '12px',
|
|
192
|
+
display: 'flex',
|
|
193
|
+
height: '24px',
|
|
194
|
+
justifyContent: isEnabled ? 'flex-end' : 'flex-start',
|
|
195
|
+
padding: '2px',
|
|
196
|
+
transition: 'all 0.2s ease',
|
|
197
|
+
width: '44px'
|
|
198
|
+
},
|
|
199
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
200
|
+
style: {
|
|
201
|
+
background: 'white',
|
|
202
|
+
borderRadius: '50%',
|
|
203
|
+
height: '20px',
|
|
204
|
+
width: '20px'
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
}),
|
|
208
|
+
/*#__PURE__*/ _jsx("span", {
|
|
209
|
+
style: {
|
|
210
|
+
fontWeight: 500
|
|
211
|
+
},
|
|
212
|
+
children: typeof collection.labels?.singular === 'string' ? collection.labels.singular : collection.labels?.singular?.en || collection.slug
|
|
213
|
+
})
|
|
214
|
+
]
|
|
215
|
+
}, collection.slug);
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
]
|
|
219
|
+
})
|
|
220
|
+
]
|
|
221
|
+
});
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
//# sourceMappingURL=index.js.map
|