@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.
Files changed (68) hide show
  1. package/dist/ai/core/media/image/handlers/multimodal.js +13 -33
  2. package/dist/ai/core/media/image/handlers/multimodal.js.map +1 -1
  3. package/dist/ai/core/media/image/handlers/standard.js +10 -7
  4. package/dist/ai/core/media/image/handlers/standard.js.map +1 -1
  5. package/dist/ai/core/media/speech/generateSpeech.js +8 -8
  6. package/dist/ai/core/media/speech/generateSpeech.js.map +1 -1
  7. package/dist/ai/core/media/types.d.ts +1 -1
  8. package/dist/ai/core/media/types.js.map +1 -1
  9. package/dist/ai/providers/blocks/google.js +1 -0
  10. package/dist/ai/providers/blocks/google.js.map +1 -1
  11. package/dist/ai/providers/registry.js +2 -2
  12. package/dist/ai/providers/registry.js.map +1 -1
  13. package/dist/collections/AIProviders.d.ts +2 -0
  14. package/dist/collections/{AISettings.js → AIProviders.js} +7 -6
  15. package/dist/collections/AIProviders.js.map +1 -0
  16. package/dist/collections/Instructions.js +8 -4
  17. package/dist/collections/Instructions.js.map +1 -1
  18. package/dist/defaults.js +1 -1
  19. package/dist/defaults.js.map +1 -1
  20. package/dist/endpoints/fetchFields.js +1 -1
  21. package/dist/endpoints/fetchFields.js.map +1 -1
  22. package/dist/endpoints/fetchVoices.js +1 -1
  23. package/dist/endpoints/fetchVoices.js.map +1 -1
  24. package/dist/endpoints/index.js +56 -21
  25. package/dist/endpoints/index.js.map +1 -1
  26. package/dist/exports/client.d.ts +1 -1
  27. package/dist/exports/client.js +1 -1
  28. package/dist/exports/client.js.map +1 -1
  29. package/dist/plugin.js +2 -2
  30. package/dist/plugin.js.map +1 -1
  31. package/dist/providers/InstructionsProvider/useInstructions.js +1 -1
  32. package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
  33. package/dist/types.d.ts +4 -4
  34. package/dist/types.js.map +1 -1
  35. package/dist/ui/AIConfigDashboard/index.d.ts +1 -1
  36. package/dist/ui/AIConfigDashboard/index.js +16 -14
  37. package/dist/ui/AIConfigDashboard/index.js.map +1 -1
  38. package/dist/ui/AIConfigDashboard/index.jsx +18 -13
  39. package/dist/ui/Compose/hooks/menu/TranslateMenu.d.ts +5 -0
  40. package/dist/ui/Compose/hooks/menu/TranslateMenu.js +45 -4
  41. package/dist/ui/Compose/hooks/menu/TranslateMenu.js.map +1 -1
  42. package/dist/ui/Compose/hooks/menu/TranslateMenu.jsx +41 -5
  43. package/dist/ui/Compose/hooks/menu/menu.module.scss +4 -1
  44. package/dist/ui/Compose/hooks/useActiveFieldTracking.js +34 -0
  45. package/dist/ui/Compose/hooks/useActiveFieldTracking.js.map +1 -1
  46. package/dist/ui/Compose/hooks/useGenerateUpload.js +8 -2
  47. package/dist/ui/Compose/hooks/useGenerateUpload.js.map +1 -1
  48. package/dist/ui/ConfigDashboard/index.d.ts +2 -0
  49. package/dist/ui/ConfigDashboard/index.js +224 -0
  50. package/dist/ui/ConfigDashboard/index.js.map +1 -0
  51. package/dist/ui/ConfigDashboard/index.jsx +175 -0
  52. package/dist/ui/DynamicModelSelect/index.js +1 -1
  53. package/dist/ui/DynamicModelSelect/index.js.map +1 -1
  54. package/dist/ui/DynamicModelSelect/index.jsx +1 -1
  55. package/dist/ui/DynamicProviderSelect/index.js +1 -1
  56. package/dist/ui/DynamicProviderSelect/index.js.map +1 -1
  57. package/dist/ui/DynamicProviderSelect/index.jsx +1 -1
  58. package/dist/ui/DynamicVoiceSelect/index.js +1 -1
  59. package/dist/ui/DynamicVoiceSelect/index.js.map +1 -1
  60. package/dist/ui/DynamicVoiceSelect/index.jsx +1 -1
  61. package/dist/ui/ProviderOptionsEditor/index.js +1 -1
  62. package/dist/ui/ProviderOptionsEditor/index.js.map +1 -1
  63. package/dist/ui/ProviderOptionsEditor/index.jsx +1 -1
  64. package/dist/utilities/updateFieldsConfig.js +1 -1
  65. package/dist/utilities/updateFieldsConfig.js.map +1 -1
  66. package/package.json +1 -1
  67. package/dist/collections/AISettings.d.ts +0 -2
  68. 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","onBlur","onChange","event","value","target","l","lowerCaseValue","toLowerCase","name","startsWith","onFocus","placeholder","map","locale","span","ellipsis","MemoizedTranslateMenu"],"mappings":";AAAA,OAAOA,aAAa,eAAc;AAClC,OAAOC,SAASC,IAAI,EAAEC,QAAQ,QAAQ,QAAO;AAE7C,SAASC,eAAe,QAAQ,gEAA+D;AAC/F,SAASC,IAAI,QAAQ,YAAW;AAChC,SAASC,SAAS,QAAQ,aAAY;AACtC,OAAOC,YAAY,qBAAoB;AAEvC,OAAO,MAAMC,gBAAgB,CAAC,EAAEC,OAAO,EAAmD;IACxF,MAAM,CAACC,MAAMC,QAAQ,GAAGR,SAAS;IAEjC,MAAM,EAAES,mBAAmB,EAAE,EAAE,GAAGR;IAElC,IAAIS,kBAAkBb,QAAQc,GAAG,CAACC,MAAM,CAAC,CAACC;QACxC,OAAOA,EAAEC,GAAG,IAAID,EAAEE,QAAQ;IAC5B;IAEA,IAAIN,kBAAkBO,QAAQ;QAC5BN,kBAAkBA,gBAAgBE,MAAM,CAAC,CAACC,IAAMJ,kBAAkBQ,SAASJ,EAAEC,GAAG;IAClF;IAEA,MAAM,CAACI,WAAWC,aAAa,GAAGnB,SAASU;IAC3C,MAAM,CAACU,YAAYC,cAAc,GAAGrB,SAAS;IAE7C,qBACE,MAACsB;QACCC,WAAWnB,OAAOoB,IAAI;QACtBC,cAAc;YACZ,IAAI,CAACL,YAAY;gBACfZ,QAAQ;YACV;QACF;;0BAEA,KAACL;gBACCuB,UAAUnB;gBACVoB,MAAM;gBACNrB,SAAS;oBACPE,QAAQ,CAACD;gBACX;;0BAEF,KAACe;gBAAIC,WAAWnB,OAAOwB,SAAS;gBAAEC,aAAWtB;0BAC3C,cAAA,MAACe;oBACCC,WAAW,CAAC,EAAEnB,OAAOoB,IAAI,CAAC,CAAC,EAAEpB,OAAO0B,OAAO,CAAC,CAAC;oBAC7CC,OAAO;wBACLC,YAAY;oBAEd;;sCAEA,KAAC9B;4BACCI,SAAS,KAAO;4BAChByB,OAAO;gCACLC,YAAY;gCACZC,SAAS;gCACTC,UAAU;gCACVC,KAAK;4BACP;sCAEA,cAAA,KAACC;gCACCb,WAAWnB,OAAOiC,SAAS;gCAC3BC,QAAQ,IAAMjB,cAAc;gCAC5BkB,UAAU,CAACC;oCACT,MAAMC,QAAQD,MAAME,MAAM,CAACD,KAAK;oCAChCtB,aACET,gBAAgBE,MAAM,CAAC,CAAC+B;wCACtB,MAAMC,iBAAiBH,MAAMI,WAAW;wCACxC,OACEF,EAAEG,IAAI,CAACD,WAAW,GAAGE,UAAU,CAACH,mBAC/BD,EAAE5B,QAAQ,IAAI4B,EAAE5B,QAAQ,CAAC8B,WAAW,GAAGE,UAAU,CAACH,mBACnDD,EAAE7B,GAAG,CAAC+B,WAAW,GAAGE,UAAU,CAACH;oCAEnC;gCAEJ;gCACAI,SAAS,IAAM3B,cAAc;gCAC7B4B,aAAY;;;wBAGf/B,UAAUgC,GAAG,CAAC,CAACC;4BACd,qBACE,KAACjD;gCAECI,SAAS;oCACPA,QAAQ;wCAAE6C,QAAQA,OAAOrC,GAAG;oCAAC;gCAC/B;0CAEA,cAAA,KAACsC;oCAAK7B,WAAWnB,OAAOiD,QAAQ;8CAAG,CAAC,EAAEF,OAAOpC,QAAQ,CAAC,EAAE,EAAEoC,OAAOrC,GAAG,CAAC,CAAC,CAAC;;+BALlEqC,OAAOrC,GAAG;wBAQrB;;;;;;AAKV,EAAC;AAED,OAAO,MAAMwC,sCAAwBvD,KAAKM,eAAc"}
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
- return (<div className={styles.menu} onMouseLeave={() => {
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
- setShow(false);
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) + 12px);
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":"AAAA;AAEA,SAASA,SAAS,QAAQ,QAAO;AAEjC;;CAEC,GACD,MAAMC,sBAAsB;IAAC;IAAU;IAAQ;IAAY;CAAoB;AAE/E,mEAAmE;AACnE,MAAMC,iBAAiB,IAAIC;AAC3B,MAAMC,iBAAiB,IAAID;AAE3B,qDAAqD;AACrD,IAAIE,2BAA0C;AAC9C,IAAIC,qBAAoC;AAExC,IAAIC,mBAAuC;AAC3C,IAAIC,QAAuB,KAAK,gCAAgC;;AAEhE;;CAEC,GACD,MAAMC,YAAY,CAACC;IACjB,IAAI,OAAOC,QAAQ,eAAe,OAAOA,IAAIC,MAAM,KAAK,YAAY;QAClE,OAAOD,IAAIC,MAAM,CAACF;IACpB;IACA,OAAOA,MAAMG,OAAO,CAAC,oCAAoC;AAC3D;AAEA;;;CAGC,GACD,MAAMC,+BAA+B,CAACC;IACpC,qEAAqE;IACrE,MAAMC,OAAOD,OAAOE,YAAY,CAAC;IACjC,IAAI,CAACD,QAAQ,CAAC;QAAC;QAAW;KAAS,CAACE,QAAQ,CAACF,OAAO;QAClD,OAAO;IACT;IAEA,MAAMG,UAAUJ,OAAOK,OAAO,CAAc;IAC5C,IAAI,CAACD,SAASE,IAAI;QAChB,OAAO;IACT;IAEA,MAAMA,KAAKZ,UAAUU,QAAQE,EAAE;IAC/B,MAAMC,WAAW,CAAC,gBAAgB,EAAED,GAAG,gBAAgB,EAAEA,GAAG,EAAE,CAAC;IAC/D,MAAME,UAAUC,SAASC,aAAa,CAAcH;IAEpD,OAAOC,SAASH,QAAqB,kBAAkB;AACzD;AAEA;;;CAGC,GACD,MAAMM,qBAAqB,CAACC;IAC1B,oBAAoB;IACpB,IAAIvB,eAAewB,GAAG,CAACD,YAAY;QACjC,OAAOvB,eAAeyB,GAAG,CAACF;IAC5B;IAEA,2BAA2B;IAC3B,MAAMG,SAAS7B,oBAAoB8B,IAAI,CACrC,CAACC,OACCL,UAAUM,SAAS,CAACC,QAAQ,CAACF,SAASL,UAAUM,SAAS,CAACC,QAAQ,CAAC,CAAC,WAAW,EAAEF,KAAK,CAAC;IAG3F5B,eAAe+B,GAAG,CAACR,WAAWG;IAC9B,OAAOA;AACT;AAEA;;;;CAIC,GACD,MAAMM,6BAA6B,CAACrB;IAClC,IAAI,CAAEA,CAAAA,kBAAkBsB,WAAU,GAAI;QACpC,OAAO;IACT;IAEA,oBAAoB;IACpB,IAAInC,eAAe0B,GAAG,CAACb,SAAS;QAC9B,MAAMuB,SAASpC,eAAe2B,GAAG,CAACd;QAClC,uCAAuC;QACvC,IAAI,CAACuB,UAAUA,OAAOC,WAAW,EAAE;YACjC,OAAOD;QACT;QACA,+BAA+B;QAC/BpC,eAAesC,MAAM,CAACzB;IACxB;IAEA,iBAAiB;IACjB,IAAIY,YAAYZ,OAAOK,OAAO,CAAc;IAE5C,4CAA4C;IAC5C,IAAI,CAACO,WAAW;QACdA,YAAYb,6BAA6BC;IAC3C;IAEA,uCAAuC;IACvC,MAAMe,SAASH,aAAaD,mBAAmBC,aAAaA,YAAY;IACxEzB,eAAeiC,GAAG,CAACpB,QAAQe;IAE3B,OAAOA;AACT;AAEA;;;;CAIC,GACD,MAAMW,qBAAqB,CAACC;IAC1B,uDAAuD;IACvD,IAAInC,oBAAoB,CAACA,iBAAiBgC,WAAW,EAAE;QACrDhC,mBAAmB;IACrB;IACA,IAAImC,QAAQ,CAACA,KAAKH,WAAW,EAAE;QAC7BG,OAAO;IACT;IAEA,IAAInC,qBAAqBmC,MAAM;QAC7B;IACF;IAEAnC,kBAAkB0B,UAAUU,OAAO;IACnC,IAAID,MAAM;QACRA,KAAKT,SAAS,CAACW,GAAG,CAAC;IACrB;IACArC,mBAAmBmC;AACrB;AAEA,MAAMG,uBAAuB;IAC3B,IAAItC,kBAAkB;QACpBA,iBAAiB0B,SAAS,CAACU,MAAM,CAAC;QAClCpC,mBAAmB;IACrB;IAEA,yBAAyB;IACzB,IAAIC,UAAU,MAAM;QAClBsC,qBAAqBtC;QACrBA,QAAQ;IACV;AACF;AAEA,MAAMuC,uBAAuB,CAACC;IAC5B,MAAMC,UAAUD,QAAQC,OAAO,CAACC,WAAW;IAC3C,MAAMC,kBAAkB;QAAC;QAAS;QAAY;QAAU;KAAS;IAEjE,IAAIA,gBAAgBjC,QAAQ,CAAC+B,UAAU;QACrC,OAAO;IACT;IAEA,4BAA4B;IAC5B,IAAID,QAAQI,iBAAiB,EAAE;QAC7B,OAAO;IACT;IAEA,2EAA2E;IAC3E,MAAMpC,OAAOgC,QAAQ/B,YAAY,CAAC;IAClC,IAAID,QAAQ;QAAC;QAAY;QAAW;QAAa;KAAU,CAACE,QAAQ,CAACF,OAAO;QAC1E,OAAO;IACT;IAEA,OAAO;AACT;AAEA;;;CAGC,GACD,MAAMqC,YAAY,CAACC;IACjB,MAAMvC,SAASuC,EAAEvC,MAAM;IACvB,IAAI,CAAEA,CAAAA,kBAAkBsB,WAAU,GAAI;QACpC;IACF;IAEA,2DAA2D;IAC3D,IAAI9B,kBAAkBgC,eAAehC,iBAAiB2B,QAAQ,CAACnB,SAAS;QACtE;IACF;IAEA,+DAA+D;IAC/D,IAAI,CAACgC,qBAAqBhC,SAAS;QACjC;IACF;IAEA,6BAA6B;IAC7B,IAAIT,uBAAuB,MAAM;QAC/BiD,aAAajD;IACf;IAEA,0EAA0E;IAC1EA,qBAAqBkD,OAAOC,UAAU,CAAC;QACrCnD,qBAAqB;QACrB,MAAMqB,YAAYS,2BAA2BrB;QAC7C0B,mBAAmBd;IACrB,GAAG;AACL;AAEA;;;CAGC,GACD,MAAM+B,gBAAgB,CAACJ;IACrB,MAAMvC,SAASuC,EAAEvC,MAAM;IACvB,IAAI,CAAEA,CAAAA,kBAAkBsB,WAAU,GAAI;QACpC;IACF;IAEA,kDAAkD;IAClD,IAAI9B,kBAAkBgC,eAAehC,iBAAiB2B,QAAQ,CAACnB,SAAS;QACtE;IACF;IAEA,sDAAsD;IACtD,4DAA4D;IAC5D,IAAI,CAACA,OAAOK,OAAO,CAAC,gBAAgB;QAClC,IAAIb,kBAAkB;YACpBkC,mBAAmB;QACrB;QACA;IACF;IAEA,uDAAuD;IACvD,IAAIpC,6BAA6B,MAAM;QACrC;IACF;IAEA,MAAMsB,YAAYS,2BAA2BrB;IAC7C0B,mBAAmBd;IAEnB,8BAA8B;IAC9BtB,2BAA2BmD,OAAOC,UAAU,CAAC;QAC3CpD,2BAA2B;IAC7B,GAAG;AACL;AAEA;;CAEC,GACD,MAAMsD,YAAY,CAACL;IACjB,IAAIA,EAAEM,GAAG,KAAK,OAAO;QACnB;IACF;IAEA,4CAA4C;IAC5C,IAAIpD,UAAU,MAAM;QAClBsC,qBAAqBtC;IACvB;IAEA,sCAAsC;IACtCA,QAAQqD,sBAAsB;QAC5BrD,QAAQ;QACR,MAAMmB,YAAYS,2BAA2BZ,SAASsC,aAAa;QACnErB,mBAAmBd;IACrB;AACF;AAEA;;CAEC,GACD,MAAMoC,qBAAqB;IACzB,IAAI,OAAOvC,aAAa,eAAeA,SAASwC,MAAM,EAAE;QACtD,mDAAmD;QACnDnB;IACF;AACF;AAEA;;;CAGC,GACD,OAAO,MAAMoB,yBAAyB;IACpCjE,UAAU;QACR,IAAI,OAAOwD,WAAW,aAAa;YACjC;QACF;QAEA,MAAMU,eAAeV;QAMrB,4CAA4C;QAC5CU,aAAaC,wBAAwB,GAAG,AAACD,CAAAA,aAAaC,wBAAwB,IAAI,CAAA,IAAK;QAEvF,iCAAiC;QACjC,IAAI,CAACD,aAAaE,mBAAmB,EAAE;YACrC,MAAMC,aAAa,IAAIC;YACvBJ,aAAaK,6BAA6B,GAAGF;YAE7C,iCAAiC;YACjC7C,SAASgD,gBAAgB,CAAC,WAAWnB,WAAW;gBAC9CoB,SAAS;gBACTC,QAAQL,WAAWK,MAAM;YAC3B;YACAlD,SAASgD,gBAAgB,CAAC,eAAed,eAAe;gBACtDe,SAAS;gBACTE,SAAS;gBACTD,QAAQL,WAAWK,MAAM;YAC3B;YACAlD,SAASgD,gBAAgB,CAAC,WAAWb,WAAW;gBAC9Cc,SAAS;gBACTC,QAAQL,WAAWK,MAAM;YAC3B;YACAlD,SAASgD,gBAAgB,CAAC,oBAAoBT,oBAAoB;gBAChEW,QAAQL,WAAWK,MAAM;YAC3B;YAEAR,aAAaE,mBAAmB,GAAG;QACrC;QAEA,OAAO;YACL,oDAAoD;YACpDF,aAAaC,wBAAwB,GAAG,AAACD,CAAAA,aAAaC,wBAAwB,IAAI,CAAA,IAAK;YAEvF,IAAI,AAACD,CAAAA,aAAaC,wBAAwB,IAAI,CAAA,KAAM,GAAG;gBACrD,kCAAkC;gBAClCD,aAAaK,6BAA6B,EAAEK;gBAC5CV,aAAaK,6BAA6B,GAAGM;gBAE7C,mDAAmD;gBACnDhC;gBAEA,kBAAkB;gBAClBqB,aAAaE,mBAAmB,GAAG;gBACnCF,aAAaC,wBAAwB,GAAG;YAC1C;QACF;IACF,GAAG,EAAE;AACP,EAAC"}
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
- setValue(result?.id);
45
- setHistory(result?.id);
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":"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;oBACV5B,SAAS4B,QAAQtC;oBACjBM,WAAWgC,QAAQtC;oBAEnB,oCAAoC;oBACpClB,MAAMyD,OAAO,CAAC;oBAEd,OAAOL;gBACT;gBAEA,mEAAmE;gBACnE,IAAIG,OAAOA,IAAIrC,EAAE,EAAE;oBACjBiB,eAAe;oBACf,MAAMuB,YAAY;oBAClB,IAAIC,WAAW;oBACf,MAAMC,cAAc,IAAI,yBAAyB;;oBAEjD,iFAAiF;oBACjF,MAAMC,OAAO;wBACX,IAAIH,WAAW;4BACb;wBACF;wBACA,IAAI;4BACF,MAAMI,MAAM,MAAMtB,MAAM,CAAC,EAAEvB,UAAU,EAAED,IAAI,CAAC,EAAER,qBAAqB,CAAC,EAAE+C,IAAIrC,EAAE,CAAC,CAAC,EAAE;gCAC9E8B,aAAa;4BACf;4BACA,IAAIc,IAAIT,EAAE,EAAE;gCACV,MAAMU,SAAS,MAAMD,IAAIR,IAAI;gCAC7B,MAAM,EAAEU,QAAQ,EAAEC,SAAS,EAAEC,MAAM,EAAE,GAAGH,UAAU,CAAC;gCACnDjC,aAAaoC;gCACbjC,eAAe+B,YAAY;gCAC3B,4CAA4C;gCAC5C,IAAIE,WAAW,eAAeD,WAAW;oCACvC,IAAIE,aAAaF;oCAEjB,uDAAuD;oCACvD,IAAIxC,SAAS,gBAAgBA,SAAS,OAAOA,MAAM2C,UAAU,KAAK,UAAU;wCAC1E,IAAIT,WAAW;wCACf,MAAMC,cAAc;wCACpB,MAAOD,WAAWC,YAAa;4CAC7B,IAAI;gDACF,MAAMS,SAAS,MAAM7B,MACnB,CAAC,EAAEvB,UAAU,EAAED,IAAI,CAAC,EAAES,MAAM2C,UAAU,CAAC,CAAC,EAAEH,UAAU,CAAC,EACrD;oDACEjB,aAAa;gDACf;gDAEF,IAAIqB,OAAOhB,EAAE,EAAE;oDACb,MAAMhB,MAAM,MAAMgC,OAAOf,IAAI;oDAC7B,mCAAmC;oDACnC,IAAIjB,OAAOA,IAAIiC,GAAG,EAAE;wDAClBH,aAAa9B;wDACb;oDACF;gDACF;4CACF,EAAE,OAAOkC,GAAG;gDACVC,QAAQC,KAAK,CAAC,mDAAmDF;4CACnE;4CACAZ;4CACA,IAAIA,WAAWC,aAAa;gDAC1B,MAAM,IAAIc,QAAQ,CAACC,UAAYC,WAAWD,SAAS;4CACrD;wCACF;oCACF;oCAEA/C,SAASuC;oCACT3C,WAAWyC;oCACX9B,eAAe;oCACf;gCACF;gCACA,IAAI+B,WAAW,UAAU;oCACvB/B,eAAe;oCACf,MAAM,IAAI0C,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,OAAOT;gBACT;gBAEA,MAAM,IAAIyB,MAAM;YAClB,OAAO;gBACL,MAAM,EAAEE,SAAS,EAAE,EAAE,GAAG,MAAM3B,eAAeE,IAAI;gBACjD,MAAM0B,SAASD,OAAOE,GAAG,CAAC,CAACR,QAAeA,MAAMS,OAAO,EAAEC,IAAI,CAAC;gBAC9D,MAAM,IAAIN,MAAMG;YAClB;QACF,GACCI,KAAK,CAAC,CAACX;YACNzE,MAAMyE,KAAK,CAAC,CAAC,oBAAoB,EAAEA,MAAMS,OAAO,CAAC,CAAC;YAClDV,QAAQC,KAAK,CACX,qGACAA;QAEJ;IACJ,GAAG;QACDnD;QACAD,kBAAkBwB;QAClBhC;QACA,YAAY;QACZM;QACAC;QACAH;QACAD;QACAQ;KACD;IAED,OAAO;QACLY;QACAF;QACAF;QACAH;IACF;AACF,EAAC"}
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":"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"}
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const ConfigDashboard: React.FC;
@@ -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