@ai-stack/payloadcms 3.68.0-beta.1 → 3.68.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/ai/core/media/image/handlers/multimodal.js +5 -0
  2. package/dist/ai/core/media/image/handlers/multimodal.js.map +1 -1
  3. package/dist/ai/core/streamObject.js +0 -3
  4. package/dist/ai/core/streamObject.js.map +1 -1
  5. package/dist/ai/providers/blocks/anthropic.js +2 -1
  6. package/dist/ai/providers/blocks/anthropic.js.map +1 -1
  7. package/dist/ai/providers/blocks/elevenlabs.js +2 -1
  8. package/dist/ai/providers/blocks/elevenlabs.js.map +1 -1
  9. package/dist/ai/providers/blocks/fal.js +2 -1
  10. package/dist/ai/providers/blocks/fal.js.map +1 -1
  11. package/dist/ai/providers/blocks/google.js +2 -1
  12. package/dist/ai/providers/blocks/google.js.map +1 -1
  13. package/dist/ai/providers/blocks/openai-compatible.js +2 -1
  14. package/dist/ai/providers/blocks/openai-compatible.js.map +1 -1
  15. package/dist/ai/providers/blocks/openai.js +2 -1
  16. package/dist/ai/providers/blocks/openai.js.map +1 -1
  17. package/dist/ai/providers/blocks/xai.js +2 -1
  18. package/dist/ai/providers/blocks/xai.js.map +1 -1
  19. package/dist/ai/providers/icons.d.ts +7 -0
  20. package/dist/ai/providers/icons.js +9 -0
  21. package/dist/ai/providers/icons.js.map +1 -0
  22. package/dist/ai/providers/registry.js +34 -23
  23. package/dist/ai/providers/registry.js.map +1 -1
  24. package/dist/collections/Instructions.js +37 -0
  25. package/dist/collections/Instructions.js.map +1 -1
  26. package/dist/endpoints/chat.d.ts +4 -0
  27. package/dist/endpoints/index.js +86 -10
  28. package/dist/endpoints/index.js.map +1 -1
  29. package/dist/exports/fields.d.ts +1 -0
  30. package/dist/exports/fields.js +1 -0
  31. package/dist/exports/fields.js.map +1 -1
  32. package/dist/fields/ArrayComposeField/ArrayComposeField.d.ts +15 -0
  33. package/dist/fields/ArrayComposeField/ArrayComposeField.js +87 -0
  34. package/dist/fields/ArrayComposeField/ArrayComposeField.js.map +1 -0
  35. package/dist/fields/ArrayComposeField/ArrayComposeField.jsx +73 -0
  36. package/dist/fields/PromptEditorField/PromptEditorField.js +7 -2
  37. package/dist/fields/PromptEditorField/PromptEditorField.js.map +1 -1
  38. package/dist/fields/PromptEditorField/PromptEditorField.jsx +5 -2
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +1 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/init.js +23 -6
  43. package/dist/init.js.map +1 -1
  44. package/dist/payload-ai.d.ts +149 -0
  45. package/dist/providers/InstructionsProvider/InstructionsProvider.js +3 -0
  46. package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
  47. package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +3 -0
  48. package/dist/providers/InstructionsProvider/useInstructions.js +18 -1
  49. package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
  50. package/dist/styles.d.ts +11 -0
  51. package/dist/types/handlebars-async-helpers.d.ts +1 -0
  52. package/dist/types/handlebars-dist-handlebars.d.ts +1 -0
  53. package/dist/types/react-mentions.d.ts +1 -0
  54. package/dist/ui/Compose/Compose.d.ts +1 -0
  55. package/dist/ui/Compose/Compose.js +2 -2
  56. package/dist/ui/Compose/Compose.js.map +1 -1
  57. package/dist/ui/Compose/Compose.jsx +2 -2
  58. package/dist/ui/Compose/UndoRedoActions.d.ts +2 -2
  59. package/dist/ui/Compose/UndoRedoActions.js +8 -5
  60. package/dist/ui/Compose/UndoRedoActions.js.map +1 -1
  61. package/dist/ui/Compose/UndoRedoActions.jsx +6 -5
  62. package/dist/ui/Compose/compose.module.css +56 -16
  63. package/dist/ui/Compose/hooks/menu/itemsMap.js +12 -6
  64. package/dist/ui/Compose/hooks/menu/itemsMap.js.map +1 -1
  65. package/dist/ui/Compose/hooks/menu/useMenu.js +26 -15
  66. package/dist/ui/Compose/hooks/menu/useMenu.js.map +1 -1
  67. package/dist/ui/Compose/hooks/menu/useMenu.jsx +25 -12
  68. package/dist/ui/Compose/hooks/useHistory.d.ts +0 -1
  69. package/dist/ui/Compose/hooks/useHistory.js +65 -25
  70. package/dist/ui/Compose/hooks/useHistory.js.map +1 -1
  71. package/dist/ui/DynamicVoiceSelect/index.js +63 -11
  72. package/dist/ui/DynamicVoiceSelect/index.js.map +1 -1
  73. package/dist/ui/DynamicVoiceSelect/index.jsx +47 -14
  74. package/dist/ui/VoicesFetcher/index.js +54 -8
  75. package/dist/ui/VoicesFetcher/index.js.map +1 -1
  76. package/dist/ui/VoicesFetcher/index.jsx +32 -9
  77. package/dist/utilities/buildSmartPrompt.d.ts +22 -0
  78. package/dist/utilities/buildSmartPrompt.js +143 -0
  79. package/dist/utilities/buildSmartPrompt.js.map +1 -0
  80. package/dist/utilities/resolveImageReferences.d.ts +3 -1
  81. package/dist/utilities/resolveImageReferences.js +21 -2
  82. package/dist/utilities/resolveImageReferences.js.map +1 -1
  83. package/dist/utilities/updateFieldsConfig.js +7 -1
  84. package/dist/utilities/updateFieldsConfig.js.map +1 -1
  85. package/package.json +3 -3
  86. package/dist/endpoints/chat.d.js +0 -3
  87. package/dist/endpoints/chat.d.js.map +0 -1
  88. package/dist/payload-ai.d.js +0 -3
  89. package/dist/payload-ai.d.js.map +0 -1
  90. package/dist/styles.d.js +0 -2
  91. package/dist/styles.d.js.map +0 -1
  92. package/dist/types/handlebars-async-helpers.d.js +0 -2
  93. package/dist/types/handlebars-async-helpers.d.js.map +0 -1
  94. package/dist/types/handlebars-dist-handlebars.d.js +0 -2
  95. package/dist/types/handlebars-dist-handlebars.d.js.map +0 -1
  96. package/dist/types/react-mentions.d.js +0 -2
  97. package/dist/types/react-mentions.d.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/useMenu.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport React, { useEffect, useMemo, useState } from 'react'\n\nimport type { ActionMenuItems, UseMenuEvents, UseMenuOptions } from '../../../../types.js'\n\nimport { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js'\nimport { Compose, Proofread, Rephrase } from './items.js'\nimport { menuItemsMap } from './itemsMap.js'\nimport styles from './menu.module.scss'\n\nconst getActiveComponent = (ac: ActionMenuItems) => {\n switch (ac) {\n case 'Compose':\n return Compose\n case 'Proofread':\n return Proofread\n case 'Rephrase':\n return Rephrase\n default:\n return Rephrase\n }\n}\n\nexport const useMenu = (menuEvents: UseMenuEvents, options: UseMenuOptions) => {\n const { field:{ type: fieldType } = {}, path: pathFromContext } = useFieldProps()\n const field = useField({ path: pathFromContext ?? '' })\n const [activeComponent, setActiveComponent] = useState<ActionMenuItems>('Rephrase')\n\n const { initialValue, value } = field\n\n useEffect(() => {\n if (!value) {\n setActiveComponent('Compose')\n return\n }\n\n if (menuItemsMap.some((i) => i.excludedFor?.includes(fieldType ?? ''))) {\n setActiveComponent('Compose')\n return\n }\n\n if (typeof value === 'string' && value !== initialValue) {\n setActiveComponent('Proofread')\n } else {\n setActiveComponent('Rephrase')\n }\n }, [initialValue, value, fieldType])\n\n const MemoizedActiveComponent = useMemo(() => {\n return ({ isLoading, loadingLabel, stop }: { isLoading: boolean; loadingLabel?: string; stop: () => void }) => {\n const ActiveComponent = getActiveComponent(activeComponent)\n const activeItem = menuItemsMap.find((i) => i.name === activeComponent)!\n return (\n <ActiveComponent\n hideIcon\n onClick={(data: unknown) => {\n if (!isLoading) {\n const trigger = menuEvents[`on${activeComponent}`]\n if (typeof trigger === 'function') {\n trigger(data)\n } else {\n console.error('No trigger found for', activeComponent)\n }\n } else {\n stop()\n }\n }}\n title={isLoading ? 'Click to stop' : activeItem.name}\n >\n {isLoading && (loadingLabel ?? activeItem.loadingText)}\n </ActiveComponent>\n )\n }\n }, [activeComponent, menuEvents])\n\n const filteredMenuItems = useMemo(\n () =>\n menuItemsMap.filter((i) => {\n if (i.name === 'Settings' && !options.isConfigAllowed) {\n return false\n } // Disable settings if a user role is not permitted\n return i.name !== activeComponent && !i.excludedFor?.includes(fieldType ?? '')\n }),\n [activeComponent, fieldType, options.isConfigAllowed],\n )\n\n const MemoizedMenu = useMemo(() => {\n return ({ isLoading, onClose }: { isLoading: boolean; onClose: () => void }) => (\n <div className={styles.menu}>\n {filteredMenuItems.map((i) => {\n const Action = i.component\n return (\n <Action\n disabled={isLoading}\n key={i.name}\n onClick={(data: unknown) => {\n if (i.name !== 'Settings') {\n setActiveComponent(i.name)\n }\n\n menuEvents[`on${i.name}`]?.(data)\n onClose()\n }}\n >\n {isLoading && i.loadingText}\n </Action>\n )\n })}\n </div>\n )\n }, [filteredMenuItems, menuEvents])\n\n return {\n ActiveComponent: MemoizedActiveComponent,\n Menu: MemoizedMenu,\n }\n}\n"],"names":["useField","React","useEffect","useMemo","useState","useFieldProps","Compose","Proofread","Rephrase","menuItemsMap","styles","getActiveComponent","ac","useMenu","menuEvents","options","field","type","fieldType","path","pathFromContext","activeComponent","setActiveComponent","initialValue","value","some","i","excludedFor","includes","MemoizedActiveComponent","isLoading","loadingLabel","stop","ActiveComponent","activeItem","find","name","hideIcon","onClick","data","trigger","console","error","title","loadingText","filteredMenuItems","filter","isConfigAllowed","MemoizedMenu","onClose","div","className","menu","map","Action","component","disabled","Menu"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../src/ui/Compose/hooks/menu/useMenu.tsx"],"sourcesContent":["'use client'\n\nimport { useForm } from '@payloadcms/ui'\nimport { getSiblingData } from 'payload/shared'\nimport React, { useEffect, useMemo, useState } from 'react'\n\nimport type { ActionMenuItems, UseMenuEvents, UseMenuOptions } from '../../../../types.js'\n\nimport { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js'\nimport { Compose, Proofread, Rephrase } from './items.js'\nimport { menuItemsMap } from './itemsMap.js'\nimport styles from './menu.module.scss'\n\nconst getActiveComponent = (ac: ActionMenuItems) => {\n switch (ac) {\n case 'Compose':\n return Compose\n case 'Proofread':\n return Proofread\n case 'Rephrase':\n return Rephrase\n default:\n return Rephrase\n }\n}\n\nexport const useMenu = (menuEvents: UseMenuEvents, options: UseMenuOptions) => {\n const { field: { type: fieldType } = {}, path } = useFieldProps()\n const { getData } = useForm()\n const [activeComponent, setActiveComponent] = useState<ActionMenuItems>('Rephrase')\n\n // Check value once on mount or when path/type changes\n useEffect(() => {\n let hasValue = false\n\n try {\n const data = getData()\n if (path) {\n const val = getSiblingData(data, path)\n hasValue = val !== undefined && val !== null\n // For richTextFields, we might need a more robust check (e.g. check for root.children.length > 0)\n // But for now, simple truthiness covers most cases or at least defaults safely\n if (fieldType === 'richText' && val && typeof val === 'object' && 'root' in val) {\n // Basic lexical check could go here if needed\n }\n }\n } catch (e) {\n // ignore\n }\n\n if (!hasValue) {\n setActiveComponent('Compose')\n return\n }\n\n if (menuItemsMap.some((i) => i.excludedFor?.includes(fieldType ?? ''))) {\n setActiveComponent('Compose')\n return\n }\n\n // Default to Rephrase if value exists\n setActiveComponent('Rephrase')\n }, [fieldType, getData, path])\n\n const MemoizedActiveComponent = useMemo(() => {\n return ({ isLoading, loadingLabel, stop }: { isLoading: boolean; loadingLabel?: string; stop: () => void }) => {\n const ActiveComponent = getActiveComponent(activeComponent)\n const activeItem = menuItemsMap.find((i) => i.name === activeComponent)!\n return (\n <ActiveComponent\n hideIcon\n onClick={(data: unknown) => {\n if (!isLoading) {\n const trigger = menuEvents[`on${activeComponent}`]\n if (typeof trigger === 'function') {\n trigger(data)\n } else {\n console.error('No trigger found for', activeComponent)\n }\n } else {\n stop()\n }\n }}\n title={isLoading ? 'Click to stop' : activeItem.name}\n >\n {isLoading && (loadingLabel ?? activeItem.loadingText)}\n </ActiveComponent>\n )\n }\n }, [activeComponent, menuEvents])\n\n const filteredMenuItems = useMemo(\n () =>\n menuItemsMap.filter((i) => {\n if (i.name === 'Settings' && !options.isConfigAllowed) {\n return false\n } // Disable settings if a user role is not permitted\n return i.name !== activeComponent && !i.excludedFor?.includes(fieldType ?? '')\n }),\n [activeComponent, fieldType, options.isConfigAllowed],\n )\n\n const MemoizedMenu = useMemo(() => {\n return ({ isLoading, onClose }: { isLoading: boolean; onClose: () => void }) => (\n <div className={styles.menu}>\n {filteredMenuItems.map((i) => {\n const Action = i.component\n return (\n <Action\n disabled={isLoading}\n key={i.name}\n onClick={(data: unknown) => {\n if (i.name !== 'Settings') {\n setActiveComponent(i.name)\n }\n\n menuEvents[`on${i.name}`]?.(data)\n onClose()\n }}\n >\n {isLoading && i.loadingText}\n </Action>\n )\n })}\n </div>\n )\n }, [filteredMenuItems, menuEvents])\n\n return {\n ActiveComponent: MemoizedActiveComponent,\n Menu: MemoizedMenu,\n }\n}\n\n"],"names":["useForm","getSiblingData","React","useEffect","useMemo","useState","useFieldProps","Compose","Proofread","Rephrase","menuItemsMap","styles","getActiveComponent","ac","useMenu","menuEvents","options","field","type","fieldType","path","getData","activeComponent","setActiveComponent","hasValue","data","val","undefined","e","some","i","excludedFor","includes","MemoizedActiveComponent","isLoading","loadingLabel","stop","ActiveComponent","activeItem","find","name","hideIcon","onClick","trigger","console","error","title","loadingText","filteredMenuItems","filter","isConfigAllowed","MemoizedMenu","onClose","div","className","menu","map","Action","component","disabled","Menu"],"mappings":"AAAA;;AAEA,SAASA,OAAO,QAAQ,iBAAgB;AACxC,SAASC,cAAc,QAAQ,iBAAgB;AAC/C,OAAOC,SAASC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAI3D,SAASC,aAAa,QAAQ,uDAAsD;AACpF,SAASC,OAAO,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,aAAY;AACzD,SAASC,YAAY,QAAQ,gBAAe;AAC5C,OAAOC,YAAY,qBAAoB;AAEvC,MAAMC,qBAAqB,CAACC;IAC1B,OAAQA;QACN,KAAK;YACH,OAAON;QACT,KAAK;YACH,OAAOC;QACT,KAAK;YACH,OAAOC;QACT;YACE,OAAOA;IACX;AACF;AAEA,OAAO,MAAMK,UAAU,CAACC,YAA2BC;IACjD,MAAM,EAAEC,OAAO,EAAEC,MAAMC,SAAS,EAAE,GAAG,CAAC,CAAC,EAAEC,IAAI,EAAE,GAAGd;IAClD,MAAM,EAAEe,OAAO,EAAE,GAAGrB;IACpB,MAAM,CAACsB,iBAAiBC,mBAAmB,GAAGlB,SAA0B;IAExE,sDAAsD;IACtDF,UAAU;QACR,IAAIqB,WAAW;QAEf,IAAI;YACF,MAAMC,OAAOJ;YACb,IAAID,MAAM;gBACR,MAAMM,MAAMzB,eAAewB,MAAML;gBACjCI,WAAWE,QAAQC,aAAaD,QAAQ;gBACxC,kGAAkG;gBAClG,+EAA+E;gBAC/E,IAAIP,cAAc,cAAcO,OAAO,OAAOA,QAAQ,YAAY,UAAUA,KAAK;gBAC9E,8CAA8C;gBACjD;YACF;QACF,EAAE,OAAOE,GAAG;QACV,SAAS;QACX;QAEA,IAAI,CAACJ,UAAU;YACbD,mBAAmB;YACnB;QACF;QAEA,IAAIb,aAAamB,IAAI,CAAC,CAACC,IAAMA,EAAEC,WAAW,EAAEC,SAASb,aAAa,MAAM;YACtEI,mBAAmB;YACnB;QACF;QAEA,sCAAsC;QACtCA,mBAAmB;IACrB,GAAG;QAACJ;QAAWE;QAASD;KAAK;IAE7B,MAAMa,0BAA0B7B,QAAQ;QACtC,OAAO,CAAC,EAAE8B,SAAS,EAAEC,YAAY,EAAEC,IAAI,EAAmE;YACxG,MAAMC,kBAAkBzB,mBAAmBU;YAC3C,MAAMgB,aAAa5B,aAAa6B,IAAI,CAAC,CAACT,IAAMA,EAAEU,IAAI,KAAKlB;YACvD,qBACE,KAACe;gBACCI,QAAQ;gBACRC,SAAS,CAACjB;oBACR,IAAI,CAACS,WAAW;wBACd,MAAMS,UAAU5B,UAAU,CAAC,CAAC,EAAE,EAAEO,gBAAgB,CAAC,CAAC;wBAClD,IAAI,OAAOqB,YAAY,YAAY;4BACjCA,QAAQlB;wBACV,OAAO;4BACLmB,QAAQC,KAAK,CAAC,wBAAwBvB;wBACxC;oBACF,OAAO;wBACLc;oBACF;gBACF;gBACAU,OAAOZ,YAAY,kBAAkBI,WAAWE,IAAI;0BAEnDN,aAAcC,CAAAA,gBAAgBG,WAAWS,WAAW,AAAD;;QAG1D;IACF,GAAG;QAACzB;QAAiBP;KAAW;IAEhC,MAAMiC,oBAAoB5C,QACxB,IACEM,aAAauC,MAAM,CAAC,CAACnB;YACnB,IAAIA,EAAEU,IAAI,KAAK,cAAc,CAACxB,QAAQkC,eAAe,EAAE;gBACrD,OAAO;YACT,EAAE,mDAAmD;YACrD,OAAOpB,EAAEU,IAAI,KAAKlB,mBAAmB,CAACQ,EAAEC,WAAW,EAAEC,SAASb,aAAa;QAC7E,IACF;QAACG;QAAiBH;QAAWH,QAAQkC,eAAe;KAAC;IAGvD,MAAMC,eAAe/C,QAAQ;QAC3B,OAAO,CAAC,EAAE8B,SAAS,EAAEkB,OAAO,EAA+C,iBACzE,KAACC;gBAAIC,WAAW3C,OAAO4C,IAAI;0BACxBP,kBAAkBQ,GAAG,CAAC,CAAC1B;oBACtB,MAAM2B,SAAS3B,EAAE4B,SAAS;oBAC1B,qBACE,KAACD;wBACCE,UAAUzB;wBAEVQ,SAAS,CAACjB;4BACR,IAAIK,EAAEU,IAAI,KAAK,YAAY;gCACzBjB,mBAAmBO,EAAEU,IAAI;4BAC3B;4BAEAzB,UAAU,CAAC,CAAC,EAAE,EAAEe,EAAEU,IAAI,CAAC,CAAC,CAAC,GAAGf;4BAC5B2B;wBACF;kCAEClB,aAAaJ,EAAEiB,WAAW;uBAVtBjB,EAAEU,IAAI;gBAajB;;IAGN,GAAG;QAACQ;QAAmBjC;KAAW;IAElC,OAAO;QACLsB,iBAAiBJ;QACjB2B,MAAMT;IACR;AACF,EAAC"}
@@ -1,5 +1,6 @@
1
1
  'use client';
2
- import { useField } from '@payloadcms/ui';
2
+ import { useForm } from '@payloadcms/ui';
3
+ import { getSiblingData } from 'payload/shared';
3
4
  import React, { useEffect, useMemo, useState } from 'react';
4
5
  import { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js';
5
6
  import { Compose, Proofread, Rephrase } from './items.js';
@@ -18,12 +19,28 @@ const getActiveComponent = (ac) => {
18
19
  }
19
20
  };
20
21
  export const useMenu = (menuEvents, options) => {
21
- const { field: { type: fieldType } = {}, path: pathFromContext } = useFieldProps();
22
- const field = useField({ path: pathFromContext ?? '' });
22
+ const { field: { type: fieldType } = {}, path } = useFieldProps();
23
+ const { getData } = useForm();
23
24
  const [activeComponent, setActiveComponent] = useState('Rephrase');
24
- const { initialValue, value } = field;
25
+ // Check value once on mount or when path/type changes
25
26
  useEffect(() => {
26
- if (!value) {
27
+ let hasValue = false;
28
+ try {
29
+ const data = getData();
30
+ if (path) {
31
+ const val = getSiblingData(data, path);
32
+ hasValue = val !== undefined && val !== null;
33
+ // For richTextFields, we might need a more robust check (e.g. check for root.children.length > 0)
34
+ // But for now, simple truthiness covers most cases or at least defaults safely
35
+ if (fieldType === 'richText' && val && typeof val === 'object' && 'root' in val) {
36
+ // Basic lexical check could go here if needed
37
+ }
38
+ }
39
+ }
40
+ catch (e) {
41
+ // ignore
42
+ }
43
+ if (!hasValue) {
27
44
  setActiveComponent('Compose');
28
45
  return;
29
46
  }
@@ -31,13 +48,9 @@ export const useMenu = (menuEvents, options) => {
31
48
  setActiveComponent('Compose');
32
49
  return;
33
50
  }
34
- if (typeof value === 'string' && value !== initialValue) {
35
- setActiveComponent('Proofread');
36
- }
37
- else {
38
- setActiveComponent('Rephrase');
39
- }
40
- }, [initialValue, value, fieldType]);
51
+ // Default to Rephrase if value exists
52
+ setActiveComponent('Rephrase');
53
+ }, [fieldType, getData, path]);
41
54
  const MemoizedActiveComponent = useMemo(() => {
42
55
  return ({ isLoading, loadingLabel, stop }) => {
43
56
  const ActiveComponent = getActiveComponent(activeComponent);
@@ -1,7 +1,6 @@
1
1
  export declare const useHistory: () => {
2
2
  canRedo: boolean;
3
3
  canUndo: boolean;
4
- currentValue: any;
5
4
  redo: () => any;
6
5
  set: (data: any) => any;
7
6
  undo: () => any;
@@ -1,17 +1,17 @@
1
1
  'use client';
2
- import { useDocumentInfo, useField } from '@payloadcms/ui';
2
+ import { useDocumentInfo, useForm } from '@payloadcms/ui';
3
3
  import { useCallback, useEffect, useRef } from 'react';
4
+ import { getSiblingData } from 'payload/shared';
4
5
  import { PLUGIN_NAME } from '../../../defaults.js';
5
6
  import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js';
6
7
  const STORAGE_KEY = `${PLUGIN_NAME}-fields-history`;
8
+ const MAX_HISTORY_SIZE = 50;
7
9
  // Global cache to prevent synchronous localStorage reads on every render
8
10
  let globalHistoryCache = null;
9
11
  export const useHistory = ()=>{
10
12
  const { id } = useDocumentInfo();
11
- const { path: pathFromContext, schemaPath } = useFieldProps();
12
- const { value: currentFieldValue } = useField({
13
- path: pathFromContext ?? ''
14
- });
13
+ const { path, schemaPath } = useFieldProps();
14
+ const { getData } = useForm();
15
15
  const fieldKey = `${id}.${schemaPath}`;
16
16
  const getLatestHistory = useCallback(()=>{
17
17
  // Return cache if available
@@ -43,21 +43,29 @@ export const useHistory = ()=>{
43
43
  if (saveTimerRef.current) {
44
44
  clearTimeout(saveTimerRef.current);
45
45
  }
46
- // Debounce the save operation by 300ms
46
+ // Debounce the save operation by 500ms
47
47
  saveTimerRef.current = setTimeout(()=>{
48
48
  // Use requestIdleCallback if available to avoid blocking the main thread
49
49
  if (typeof requestIdleCallback !== 'undefined') {
50
50
  requestIdleCallback(()=>{
51
- localStorage.setItem(STORAGE_KEY, JSON.stringify(newGlobalHistory));
51
+ try {
52
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(newGlobalHistory));
53
+ } catch (e) {
54
+ console.warn('Failed to save history to localStorage', e);
55
+ }
52
56
  }, {
53
57
  timeout: 2000
54
58
  });
55
59
  } else {
56
60
  // Fallback for browsers without requestIdleCallback
57
- localStorage.setItem(STORAGE_KEY, JSON.stringify(newGlobalHistory));
61
+ try {
62
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(newGlobalHistory));
63
+ } catch (e) {
64
+ console.warn('Failed to save history to localStorage', e);
65
+ }
58
66
  }
59
67
  saveTimerRef.current = null;
60
- }, 300);
68
+ }, 500);
61
69
  }, []);
62
70
  // Sync with other tabs
63
71
  useEffect(()=>{
@@ -80,15 +88,18 @@ export const useHistory = ()=>{
80
88
  const latestHistory = {
81
89
  ...getLatestHistory()
82
90
  };
91
+ let hasChanges = false;
83
92
  Object.keys(latestHistory).forEach((k)=>{
84
93
  if (!k.startsWith(id?.toString() ?? '')) {
85
94
  delete latestHistory[k];
95
+ hasChanges = true;
86
96
  }
87
97
  });
88
- saveToLocalStorage(latestHistory);
98
+ if (hasChanges) {
99
+ saveToLocalStorage(latestHistory);
100
+ }
89
101
  }, [
90
102
  id,
91
- fieldKey,
92
103
  getLatestHistory,
93
104
  saveToLocalStorage
94
105
  ]);
@@ -101,22 +112,46 @@ export const useHistory = ()=>{
101
112
  history: []
102
113
  };
103
114
  let newIndex = currentIndex;
115
+ let historyUpdated = false;
116
+ const newHistoryArray = [
117
+ ...history
118
+ ];
104
119
  if (currentIndex == -1) {
105
120
  newIndex = 0;
106
- if (currentFieldValue) {
107
- history[newIndex] = currentFieldValue;
121
+ // Get initial value from form data instead of subscribing to useField
122
+ // This implementation avoids re-rendering on every keystroke
123
+ try {
124
+ const data = getData();
125
+ // We need to resolve the value from the data object using the path
126
+ // path might be 'group.subgroup.field'
127
+ if (path) {
128
+ const value = getSiblingData(data, path);
129
+ if (value) {
130
+ newHistoryArray[newIndex] = value;
131
+ historyUpdated = true;
132
+ }
133
+ }
134
+ } catch (e) {
135
+ // If we can't get the data, just ignore
108
136
  }
109
137
  }
110
- const newGlobalHistory = {
111
- ...latestHistory,
112
- [fieldKey]: {
113
- currentIndex: newIndex,
114
- history
115
- }
116
- };
117
- saveToLocalStorage(newGlobalHistory);
138
+ if (historyUpdated) {
139
+ const newGlobalHistory = {
140
+ ...latestHistory,
141
+ [fieldKey]: {
142
+ currentIndex: newIndex,
143
+ history: newHistoryArray
144
+ }
145
+ };
146
+ saveToLocalStorage(newGlobalHistory);
147
+ }
118
148
  }, [
119
- fieldKey
149
+ fieldKey,
150
+ getData,
151
+ path,
152
+ clearHistory,
153
+ getLatestHistory,
154
+ saveToLocalStorage
120
155
  ]);
121
156
  const set = useCallback((data)=>{
122
157
  const latestHistory = getLatestHistory();
@@ -124,10 +159,15 @@ export const useHistory = ()=>{
124
159
  currentIndex: -1,
125
160
  history: []
126
161
  };
127
- const newHistory = [
162
+ // Create new history array slice, appending new data
163
+ let newHistory = [
128
164
  ...history.slice(0, currentIndex + 1),
129
165
  data
130
166
  ];
167
+ // Enforce Max History Size
168
+ if (newHistory.length > MAX_HISTORY_SIZE) {
169
+ newHistory = newHistory.slice(newHistory.length - MAX_HISTORY_SIZE);
170
+ }
131
171
  const newGlobalHistory = {
132
172
  ...latestHistory,
133
173
  [fieldKey]: {
@@ -205,11 +245,11 @@ export const useHistory = ()=>{
205
245
  const fieldHistory = getLatestFieldHistory();
206
246
  const canUndo = fieldHistory.currentIndex > 0;
207
247
  const canRedo = fieldHistory.currentIndex < fieldHistory.history.length - 1;
208
- const currentValue = fieldHistory.history[fieldHistory.currentIndex];
248
+ // Note: We deliberately do not return currentValue to avoid subscription re-renders
249
+ // The consumers of this hook (UndoRedoActions) didn't use it anyway.
209
250
  return {
210
251
  canRedo,
211
252
  canUndo,
212
- currentValue,
213
253
  redo,
214
254
  set,
215
255
  undo
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/Compose/hooks/useHistory.ts"],"sourcesContent":["'use client'\n\nimport { useDocumentInfo, useField } from '@payloadcms/ui'\nimport { useCallback, useEffect, useRef } from 'react'\n\nimport { PLUGIN_NAME } from '../../../defaults.js'\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\n\nconst STORAGE_KEY = `${PLUGIN_NAME}-fields-history`\n\ninterface HistoryState {\n [path: string]: {\n currentIndex: number\n history: any[]\n }\n}\n\n// Global cache to prevent synchronous localStorage reads on every render\nlet globalHistoryCache: HistoryState | null = null\n\nexport const useHistory = () => {\n const { id } = useDocumentInfo()\n const { path: pathFromContext, schemaPath } = useFieldProps()\n const { value: currentFieldValue } = useField<string>({\n path: pathFromContext ?? '',\n })\n\n const fieldKey = `${id}.${schemaPath}`\n\n const getLatestHistory = useCallback((): HistoryState => {\n // Return cache if available\n if (globalHistoryCache) {\n return globalHistoryCache\n }\n\n try {\n if (typeof localStorage !== 'undefined') {\n // Read once, cache it\n const stored = localStorage.getItem(STORAGE_KEY)\n globalHistoryCache = stored ? JSON.parse(stored) : {}\n return globalHistoryCache!\n }\n return {}\n } catch (e) {\n console.error('Error parsing history:', e)\n return {}\n }\n }, [])\n\n // Debounce timer ref to prevent excessive localStorage writes\n const saveTimerRef = useRef<null | ReturnType<typeof setTimeout>>(null)\n\n const saveToLocalStorage = useCallback((newGlobalHistory: HistoryState) => {\n // Update cache immediately\n globalHistoryCache = newGlobalHistory\n\n if (typeof localStorage === 'undefined') {\n return\n }\n\n // Clear any pending save\n if (saveTimerRef.current) {\n clearTimeout(saveTimerRef.current)\n }\n\n // Debounce the save operation by 300ms\n saveTimerRef.current = setTimeout(() => {\n // Use requestIdleCallback if available to avoid blocking the main thread\n if (typeof requestIdleCallback !== 'undefined') {\n requestIdleCallback(\n () => {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(newGlobalHistory))\n },\n { timeout: 2000 },\n )\n } else {\n // Fallback for browsers without requestIdleCallback\n localStorage.setItem(STORAGE_KEY, JSON.stringify(newGlobalHistory))\n }\n saveTimerRef.current = null\n }, 300)\n }, [])\n\n // Sync with other tabs\n useEffect(() => {\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === STORAGE_KEY && e.newValue) {\n try {\n globalHistoryCache = JSON.parse(e.newValue)\n } catch (err) {\n // ignore parse error\n }\n }\n }\n\n window.addEventListener('storage', handleStorageChange)\n return () => {\n window.removeEventListener('storage', handleStorageChange)\n }\n }, [])\n\n // Clear previous history\n const clearHistory = useCallback(() => {\n const latestHistory = { ...getLatestHistory() }\n Object.keys(latestHistory).forEach((k) => {\n if (!k.startsWith(id?.toString() ?? '')) {\n delete latestHistory[k]\n }\n })\n saveToLocalStorage(latestHistory)\n }, [id, fieldKey, getLatestHistory, saveToLocalStorage])\n\n useEffect(() => {\n // This is applied to clear out the document history which is not currently in use\n clearHistory()\n\n const latestHistory = getLatestHistory()\n const { currentIndex, history } = latestHistory[fieldKey] || {\n currentIndex: -1,\n history: [],\n }\n\n let newIndex = currentIndex\n if (currentIndex == -1) {\n newIndex = 0\n if (currentFieldValue) {\n history[newIndex] = currentFieldValue\n }\n }\n\n const newGlobalHistory = {\n ...latestHistory,\n [fieldKey]: { currentIndex: newIndex, history },\n }\n\n saveToLocalStorage(newGlobalHistory)\n }, [fieldKey])\n\n const set = useCallback(\n (data: any) => {\n const latestHistory = getLatestHistory()\n const { currentIndex, history } = latestHistory[fieldKey] || {\n currentIndex: -1,\n history: [],\n }\n const newHistory = [...history.slice(0, currentIndex + 1), data]\n const newGlobalHistory = {\n ...latestHistory,\n [fieldKey]: { currentIndex: newHistory.length - 1, history: newHistory },\n }\n saveToLocalStorage(newGlobalHistory)\n return data\n },\n [fieldKey, getLatestHistory, saveToLocalStorage],\n )\n\n const undo = useCallback(() => {\n const latestHistory = getLatestHistory()\n const { currentIndex, history } = latestHistory[fieldKey] || { currentIndex: -1, history: [] }\n if (currentIndex > 0) {\n const newIndex = currentIndex - 1\n const newValue = history[newIndex]\n const newGlobalHistory = {\n ...latestHistory,\n [fieldKey]: { currentIndex: newIndex, history },\n }\n saveToLocalStorage(newGlobalHistory)\n return newValue\n }\n return undefined\n }, [fieldKey, getLatestHistory, saveToLocalStorage])\n\n const redo = useCallback(() => {\n const latestHistory = getLatestHistory()\n const { currentIndex, history } = latestHistory[fieldKey] || { currentIndex: -1, history: [] }\n if (currentIndex < history.length - 1) {\n const newIndex = currentIndex + 1\n const newValue = history[newIndex]\n const newGlobalHistory = {\n ...latestHistory,\n [fieldKey]: { currentIndex: newIndex, history },\n }\n saveToLocalStorage(newGlobalHistory)\n return newValue\n }\n return undefined\n }, [fieldKey, getLatestHistory, saveToLocalStorage])\n\n const getLatestFieldHistory = useCallback(() => {\n const latestHistory = getLatestHistory()\n return latestHistory[fieldKey] || { currentIndex: -1, history: [] }\n }, [getLatestHistory, fieldKey])\n\n const fieldHistory = getLatestFieldHistory()\n\n const canUndo = fieldHistory.currentIndex > 0\n const canRedo = fieldHistory.currentIndex < fieldHistory.history.length - 1\n const currentValue = fieldHistory.history[fieldHistory.currentIndex]\n\n return {\n canRedo,\n canUndo,\n currentValue,\n redo,\n set,\n undo,\n }\n}\n"],"names":["useDocumentInfo","useField","useCallback","useEffect","useRef","PLUGIN_NAME","useFieldProps","STORAGE_KEY","globalHistoryCache","useHistory","id","path","pathFromContext","schemaPath","value","currentFieldValue","fieldKey","getLatestHistory","localStorage","stored","getItem","JSON","parse","e","console","error","saveTimerRef","saveToLocalStorage","newGlobalHistory","current","clearTimeout","setTimeout","requestIdleCallback","setItem","stringify","timeout","handleStorageChange","key","newValue","err","window","addEventListener","removeEventListener","clearHistory","latestHistory","Object","keys","forEach","k","startsWith","toString","currentIndex","history","newIndex","set","data","newHistory","slice","length","undo","undefined","redo","getLatestFieldHistory","fieldHistory","canUndo","canRedo","currentValue"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/ui/Compose/hooks/useHistory.ts"],"sourcesContent":["'use client'\n\nimport { useDocumentInfo, useForm } from '@payloadcms/ui'\nimport { useCallback, useEffect, useRef } from 'react'\nimport { getSiblingData } from 'payload/shared'\n\nimport { PLUGIN_NAME } from '../../../defaults.js'\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\n\nconst STORAGE_KEY = `${PLUGIN_NAME}-fields-history`\nconst MAX_HISTORY_SIZE = 50\n\ninterface HistoryState {\n [path: string]: {\n currentIndex: number\n history: any[]\n }\n}\n\n// Global cache to prevent synchronous localStorage reads on every render\nlet globalHistoryCache: HistoryState | null = null\n\nexport const useHistory = () => {\n const { id } = useDocumentInfo()\n const { path, schemaPath } = useFieldProps()\n const { getData } = useForm()\n\n const fieldKey = `${id}.${schemaPath}`\n\n const getLatestHistory = useCallback((): HistoryState => {\n // Return cache if available\n if (globalHistoryCache) {\n return globalHistoryCache\n }\n\n try {\n if (typeof localStorage !== 'undefined') {\n // Read once, cache it\n const stored = localStorage.getItem(STORAGE_KEY)\n globalHistoryCache = stored ? JSON.parse(stored) : {}\n return globalHistoryCache!\n }\n return {}\n } catch (e) {\n console.error('Error parsing history:', e)\n return {}\n }\n }, [])\n\n // Debounce timer ref to prevent excessive localStorage writes\n const saveTimerRef = useRef<null | ReturnType<typeof setTimeout>>(null)\n\n const saveToLocalStorage = useCallback((newGlobalHistory: HistoryState) => {\n // Update cache immediately\n globalHistoryCache = newGlobalHistory\n\n if (typeof localStorage === 'undefined') {\n return\n }\n\n // Clear any pending save\n if (saveTimerRef.current) {\n clearTimeout(saveTimerRef.current)\n }\n\n // Debounce the save operation by 500ms\n saveTimerRef.current = setTimeout(() => {\n // Use requestIdleCallback if available to avoid blocking the main thread\n if (typeof requestIdleCallback !== 'undefined') {\n requestIdleCallback(\n () => {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(newGlobalHistory))\n } catch (e) {\n console.warn('Failed to save history to localStorage', e)\n }\n },\n { timeout: 2000 },\n )\n } else {\n // Fallback for browsers without requestIdleCallback\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(newGlobalHistory))\n } catch (e) {\n console.warn('Failed to save history to localStorage', e)\n }\n }\n saveTimerRef.current = null\n }, 500)\n }, [])\n\n // Sync with other tabs\n useEffect(() => {\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === STORAGE_KEY && e.newValue) {\n try {\n globalHistoryCache = JSON.parse(e.newValue)\n } catch (err) {\n // ignore parse error\n }\n }\n }\n\n window.addEventListener('storage', handleStorageChange)\n return () => {\n window.removeEventListener('storage', handleStorageChange)\n }\n }, [])\n\n // Clear previous history\n const clearHistory = useCallback(() => {\n const latestHistory = { ...getLatestHistory() }\n let hasChanges = false\n Object.keys(latestHistory).forEach((k) => {\n if (!k.startsWith(id?.toString() ?? '')) {\n delete latestHistory[k]\n hasChanges = true\n }\n })\n \n if (hasChanges) {\n saveToLocalStorage(latestHistory)\n }\n }, [id, getLatestHistory, saveToLocalStorage])\n\n useEffect(() => {\n // This is applied to clear out the document history which is not currently in use\n clearHistory()\n\n const latestHistory = getLatestHistory()\n const { currentIndex, history } = latestHistory[fieldKey] || {\n currentIndex: -1,\n history: [],\n }\n\n let newIndex = currentIndex\n let historyUpdated = false\n const newHistoryArray = [...history]\n\n if (currentIndex == -1) {\n newIndex = 0\n \n // Get initial value from form data instead of subscribing to useField\n // This implementation avoids re-rendering on every keystroke\n try {\n const data = getData()\n // We need to resolve the value from the data object using the path\n // path might be 'group.subgroup.field'\n if (path) {\n const value = getSiblingData(data, path)\n if (value) {\n newHistoryArray[newIndex] = value\n historyUpdated = true\n }\n }\n } catch (e) {\n // If we can't get the data, just ignore\n }\n }\n\n if (historyUpdated) {\n const newGlobalHistory = {\n ...latestHistory,\n [fieldKey]: { currentIndex: newIndex, history: newHistoryArray },\n }\n saveToLocalStorage(newGlobalHistory)\n }\n }, [fieldKey, getData, path, clearHistory, getLatestHistory, saveToLocalStorage])\n\n const set = useCallback(\n (data: any) => {\n const latestHistory = getLatestHistory()\n const { currentIndex, history } = latestHistory[fieldKey] || {\n currentIndex: -1,\n history: [],\n }\n \n // Create new history array slice, appending new data\n let newHistory = [...history.slice(0, currentIndex + 1), data]\n \n // Enforce Max History Size\n if (newHistory.length > MAX_HISTORY_SIZE) {\n newHistory = newHistory.slice(newHistory.length - MAX_HISTORY_SIZE)\n }\n \n const newGlobalHistory = {\n ...latestHistory,\n [fieldKey]: { currentIndex: newHistory.length - 1, history: newHistory },\n }\n saveToLocalStorage(newGlobalHistory)\n return data\n },\n [fieldKey, getLatestHistory, saveToLocalStorage],\n )\n\n const undo = useCallback(() => {\n const latestHistory = getLatestHistory()\n const { currentIndex, history } = latestHistory[fieldKey] || { currentIndex: -1, history: [] }\n if (currentIndex > 0) {\n const newIndex = currentIndex - 1\n const newValue = history[newIndex]\n const newGlobalHistory = {\n ...latestHistory,\n [fieldKey]: { currentIndex: newIndex, history },\n }\n saveToLocalStorage(newGlobalHistory)\n return newValue\n }\n return undefined\n }, [fieldKey, getLatestHistory, saveToLocalStorage])\n\n const redo = useCallback(() => {\n const latestHistory = getLatestHistory()\n const { currentIndex, history } = latestHistory[fieldKey] || { currentIndex: -1, history: [] }\n if (currentIndex < history.length - 1) {\n const newIndex = currentIndex + 1\n const newValue = history[newIndex]\n const newGlobalHistory = {\n ...latestHistory,\n [fieldKey]: { currentIndex: newIndex, history },\n }\n saveToLocalStorage(newGlobalHistory)\n return newValue\n }\n return undefined\n }, [fieldKey, getLatestHistory, saveToLocalStorage])\n\n const getLatestFieldHistory = useCallback(() => {\n const latestHistory = getLatestHistory()\n return latestHistory[fieldKey] || { currentIndex: -1, history: [] }\n }, [getLatestHistory, fieldKey])\n\n const fieldHistory = getLatestFieldHistory()\n\n const canUndo = fieldHistory.currentIndex > 0\n const canRedo = fieldHistory.currentIndex < fieldHistory.history.length - 1\n \n // Note: We deliberately do not return currentValue to avoid subscription re-renders\n // The consumers of this hook (UndoRedoActions) didn't use it anyway.\n\n return {\n canRedo,\n canUndo,\n redo,\n set,\n undo,\n }\n}\n"],"names":["useDocumentInfo","useForm","useCallback","useEffect","useRef","getSiblingData","PLUGIN_NAME","useFieldProps","STORAGE_KEY","MAX_HISTORY_SIZE","globalHistoryCache","useHistory","id","path","schemaPath","getData","fieldKey","getLatestHistory","localStorage","stored","getItem","JSON","parse","e","console","error","saveTimerRef","saveToLocalStorage","newGlobalHistory","current","clearTimeout","setTimeout","requestIdleCallback","setItem","stringify","warn","timeout","handleStorageChange","key","newValue","err","window","addEventListener","removeEventListener","clearHistory","latestHistory","hasChanges","Object","keys","forEach","k","startsWith","toString","currentIndex","history","newIndex","historyUpdated","newHistoryArray","data","value","set","newHistory","slice","length","undo","undefined","redo","getLatestFieldHistory","fieldHistory","canUndo","canRedo"],"mappings":"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"}
@@ -6,27 +6,47 @@ export const DynamicVoiceSelect = (props)=>{
6
6
  const { name, path } = props;
7
7
  // Get provider from siblings
8
8
  const parentPath = path.split('.').slice(0, -1).join('.');
9
- // Accessing provider from sibling data in a group
10
- const providerField = useFormFields(([fields])=>fields[`${parentPath}.provider`]);
11
- const provider = providerField?.value;
9
+ const providerPath = `${parentPath}.provider`;
10
+ // Use useFormFields to get the provider field value - this will re-render when provider changes
11
+ const providerField = useFormFields(([fields])=>fields[providerPath]);
12
+ const provider = providerField?.value || '';
12
13
  const { setValue, value } = useField({
13
14
  path
14
15
  });
15
16
  const [aiSettings, setAiSettings] = useState(null);
17
+ const [isLoading, setIsLoading] = useState(true);
18
+ // Fetch AI Settings - re-fetch when provider changes to ensure we have latest voices
16
19
  useEffect(()=>{
17
- fetch('/api/globals/ai-settings?depth=1').then((res)=>res.json()).then((data)=>setAiSettings(data)).catch((err)=>console.error('Error fetching AI settings:', err));
18
- }, []);
20
+ const fetchSettings = async ()=>{
21
+ setIsLoading(true);
22
+ try {
23
+ const response = await fetch('/api/globals/ai-settings?depth=1');
24
+ if (response.ok) {
25
+ const data = await response.json();
26
+ setAiSettings(data);
27
+ }
28
+ } catch (err) {
29
+ console.error('Error fetching AI settings:', err);
30
+ } finally{
31
+ setIsLoading(false);
32
+ }
33
+ };
34
+ void fetchSettings();
35
+ }, [
36
+ provider
37
+ ]) // Re-fetch when provider changes to ensure we have the latest voices
38
+ ;
19
39
  const voices = useMemo(()=>{
20
- if (!provider || !aiSettings) {
40
+ if (!provider || !aiSettings?.providers) {
21
41
  return [];
22
42
  }
23
- const providerBlock = aiSettings.providers?.find((p)=>p.blockType === provider && p.enabled);
24
- if (!providerBlock) {
43
+ // Find the provider block matching the selected provider
44
+ const providerBlock = aiSettings.providers.find((p)=>p.blockType === provider && p.enabled !== false);
45
+ if (!providerBlock?.voices) {
25
46
  return [];
26
47
  }
27
- // Get voices from provider block
28
- const voicesArray = providerBlock.voices || [];
29
- return voicesArray.filter((v)=>v.enabled !== false).map((v)=>({
48
+ // Get enabled voices from provider block
49
+ return providerBlock.voices.filter((v)=>v.enabled !== false).map((v)=>({
30
50
  label: v.name || v.id,
31
51
  value: v.id
32
52
  }));
@@ -34,6 +54,19 @@ export const DynamicVoiceSelect = (props)=>{
34
54
  provider,
35
55
  aiSettings
36
56
  ]);
57
+ // Clear voice selection when provider changes and current voice is not available
58
+ useEffect(()=>{
59
+ if (value && voices.length > 0) {
60
+ const voiceExists = voices.some((v)=>v.value === value);
61
+ if (!voiceExists) {
62
+ setValue('');
63
+ }
64
+ }
65
+ }, [
66
+ voices,
67
+ value,
68
+ setValue
69
+ ]);
37
70
  if (!provider) {
38
71
  return /*#__PURE__*/ _jsxs("div", {
39
72
  className: "field-type text",
@@ -53,6 +86,25 @@ export const DynamicVoiceSelect = (props)=>{
53
86
  ]
54
87
  });
55
88
  }
89
+ if (isLoading) {
90
+ return /*#__PURE__*/ _jsxs("div", {
91
+ className: "field-type text",
92
+ children: [
93
+ /*#__PURE__*/ _jsx("label", {
94
+ className: "field-label",
95
+ htmlFor: path,
96
+ children: "Voice"
97
+ }),
98
+ /*#__PURE__*/ _jsx("p", {
99
+ style: {
100
+ color: 'var(--theme-elevation-600)',
101
+ fontSize: '13px'
102
+ },
103
+ children: "Loading voices..."
104
+ })
105
+ ]
106
+ });
107
+ }
56
108
  if (voices.length === 0) {
57
109
  return /*#__PURE__*/ _jsxs("div", {
58
110
  className: "field-type text",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/DynamicVoiceSelect/index.tsx"],"sourcesContent":["'use client'\n\nimport { SelectInput, useField, useFormFields } from '@payloadcms/ui'\nimport React, { useEffect, useMemo, useState } from 'react'\n\ntype Props = {\n name: string\n path: string\n}\n\nexport const DynamicVoiceSelect: React.FC<Props> = (props) => {\n const { name, path } = props\n\n // Get provider from siblings\n const parentPath = path.split('.').slice(0, -1).join('.')\n // Accessing provider from sibling data in a group\n const providerField = useFormFields(([fields]) => fields[`${parentPath}.provider`])\n const provider = providerField?.value as string\n\n const { setValue, value } = useField<string>({ path })\n const [aiSettings, setAiSettings] = useState<any>(null)\n\n useEffect(() => {\n fetch('/api/globals/ai-settings?depth=1')\n .then((res) => res.json())\n .then((data) => setAiSettings(data))\n .catch((err) => console.error('Error fetching AI settings:', err))\n }, [])\n\n const voices = useMemo(() => {\n if (!provider || !aiSettings) {\n return []\n }\n\n const providerBlock = aiSettings.providers?.find(\n (p: any) => p.blockType === provider && p.enabled,\n )\n if (!providerBlock) {\n return []\n }\n\n // Get voices from provider block\n const voicesArray = providerBlock.voices || []\n\n return voicesArray\n .filter((v: any) => v.enabled !== false)\n .map((v: any) => ({\n label: v.name || v.id,\n value: v.id,\n }))\n }, [provider, aiSettings])\n\n if (!provider) {\n return (\n <div className=\"field-type text\">\n <label className=\"field-label\" htmlFor={path}>\n Voice\n </label>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px' }}>\n Please select a provider first.\n </p>\n </div>\n )\n }\n\n if (voices.length === 0) {\n return (\n <div className=\"field-type text\">\n <label className=\"field-label\" htmlFor={path}>\n Voice\n </label>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px' }}>\n No voices available. Please configure voices in AI Settings for {provider}.\n </p>\n </div>\n )\n }\n\n return (\n <div className=\"field-type select\">\n <label className=\"field-label\" htmlFor={path}>\n Voice\n </label>\n <SelectInput\n name={name}\n onChange={(option) => {\n if (option && typeof option === 'object' && 'value' in option) {\n setValue(option.value as string)\n } else {\n setValue(option)\n }\n }}\n options={voices}\n path={path}\n value={value}\n />\n </div>\n )\n}\n"],"names":["SelectInput","useField","useFormFields","React","useEffect","useMemo","useState","DynamicVoiceSelect","props","name","path","parentPath","split","slice","join","providerField","fields","provider","value","setValue","aiSettings","setAiSettings","fetch","then","res","json","data","catch","err","console","error","voices","providerBlock","providers","find","p","blockType","enabled","voicesArray","filter","v","map","label","id","div","className","htmlFor","style","color","fontSize","length","onChange","option","options"],"mappings":"AAAA;;AAEA,SAASA,WAAW,EAAEC,QAAQ,EAAEC,aAAa,QAAQ,iBAAgB;AACrE,OAAOC,SAASC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAO3D,OAAO,MAAMC,qBAAsC,CAACC;IAClD,MAAM,EAAEC,IAAI,EAAEC,IAAI,EAAE,GAAGF;IAEvB,6BAA6B;IAC7B,MAAMG,aAAaD,KAAKE,KAAK,CAAC,KAAKC,KAAK,CAAC,GAAG,CAAC,GAAGC,IAAI,CAAC;IACrD,kDAAkD;IAClD,MAAMC,gBAAgBb,cAAc,CAAC,CAACc,OAAO,GAAKA,MAAM,CAAC,CAAC,EAAEL,WAAW,SAAS,CAAC,CAAC;IAClF,MAAMM,WAAWF,eAAeG;IAEhC,MAAM,EAAEC,QAAQ,EAAED,KAAK,EAAE,GAAGjB,SAAiB;QAAES;IAAK;IACpD,MAAM,CAACU,YAAYC,cAAc,GAAGf,SAAc;IAElDF,UAAU;QACRkB,MAAM,oCACHC,IAAI,CAAC,CAACC,MAAQA,IAAIC,IAAI,IACtBF,IAAI,CAAC,CAACG,OAASL,cAAcK,OAC7BC,KAAK,CAAC,CAACC,MAAQC,QAAQC,KAAK,CAAC,+BAA+BF;IACjE,GAAG,EAAE;IAEL,MAAMG,SAAS1B,QAAQ;QACrB,IAAI,CAACY,YAAY,CAACG,YAAY;YAC5B,OAAO,EAAE;QACX;QAEA,MAAMY,gBAAgBZ,WAAWa,SAAS,EAAEC,KAC1C,CAACC,IAAWA,EAAEC,SAAS,KAAKnB,YAAYkB,EAAEE,OAAO;QAEnD,IAAI,CAACL,eAAe;YAClB,OAAO,EAAE;QACX;QAEA,iCAAiC;QACjC,MAAMM,cAAcN,cAAcD,MAAM,IAAI,EAAE;QAE9C,OAAOO,YACJC,MAAM,CAAC,CAACC,IAAWA,EAAEH,OAAO,KAAK,OACjCI,GAAG,CAAC,CAACD,IAAY,CAAA;gBAChBE,OAAOF,EAAE/B,IAAI,IAAI+B,EAAEG,EAAE;gBACrBzB,OAAOsB,EAAEG,EAAE;YACb,CAAA;IACJ,GAAG;QAAC1B;QAAUG;KAAW;IAEzB,IAAI,CAACH,UAAU;QACb,qBACE,MAAC2B;YAAIC,WAAU;;8BACb,KAACH;oBAAMG,WAAU;oBAAcC,SAASpC;8BAAM;;8BAG9C,KAACyB;oBAAEY,OAAO;wBAAEC,OAAO;wBAA8BC,UAAU;oBAAO;8BAAG;;;;IAK3E;IAEA,IAAIlB,OAAOmB,MAAM,KAAK,GAAG;QACvB,qBACE,MAACN;YAAIC,WAAU;;8BACb,KAACH;oBAAMG,WAAU;oBAAcC,SAASpC;8BAAM;;8BAG9C,MAACyB;oBAAEY,OAAO;wBAAEC,OAAO;wBAA8BC,UAAU;oBAAO;;wBAAG;wBACFhC;wBAAS;;;;;IAIlF;IAEA,qBACE,MAAC2B;QAAIC,WAAU;;0BACb,KAACH;gBAAMG,WAAU;gBAAcC,SAASpC;0BAAM;;0BAG9C,KAACV;gBACCS,MAAMA;gBACN0C,UAAU,CAACC;oBACT,IAAIA,UAAU,OAAOA,WAAW,YAAY,WAAWA,QAAQ;wBAC7DjC,SAASiC,OAAOlC,KAAK;oBACvB,OAAO;wBACLC,SAASiC;oBACX;gBACF;gBACAC,SAAStB;gBACTrB,MAAMA;gBACNQ,OAAOA;;;;AAIf,EAAC"}
1
+ {"version":3,"sources":["../../../src/ui/DynamicVoiceSelect/index.tsx"],"sourcesContent":["'use client'\n\nimport { SelectInput, useField, useFormFields } from '@payloadcms/ui'\nimport React, { useEffect, useMemo, useState } from 'react'\n\ntype Props = {\n name: string\n path: string\n}\n\ninterface Voice {\n category?: string\n enabled?: boolean\n id: string\n labels?: Record<string, unknown>\n name: string\n preview_url?: string\n}\n\ninterface ProviderBlock {\n blockType: string\n enabled?: boolean\n voices?: Voice[]\n}\n\nexport const DynamicVoiceSelect: React.FC<Props> = (props) => {\n const { name, path } = props\n\n // Get provider from siblings\n const parentPath = path.split('.').slice(0, -1).join('.')\n const providerPath = `${parentPath}.provider`\n\n // Use useFormFields to get the provider field value - this will re-render when provider changes\n const providerField = useFormFields(([fields]) => fields[providerPath])\n const provider = (providerField?.value as string) || ''\n\n const { setValue, value } = useField<string>({ path })\n const [aiSettings, setAiSettings] = useState<{ providers?: ProviderBlock[] } | null>(null)\n const [isLoading, setIsLoading] = useState(true)\n\n // Fetch AI Settings - re-fetch when provider changes to ensure we have latest voices\n useEffect(() => {\n const fetchSettings = async () => {\n setIsLoading(true)\n try {\n const response = await fetch('/api/globals/ai-settings?depth=1')\n if (response.ok) {\n const data = await response.json()\n setAiSettings(data)\n }\n } catch (err) {\n console.error('Error fetching AI settings:', err)\n } finally {\n setIsLoading(false)\n }\n }\n\n void fetchSettings()\n }, [provider]) // Re-fetch when provider changes to ensure we have the latest voices\n\n const voices = useMemo(() => {\n if (!provider || !aiSettings?.providers) {\n return []\n }\n\n // Find the provider block matching the selected provider\n const providerBlock = aiSettings.providers.find(\n (p: ProviderBlock) => p.blockType === provider && p.enabled !== false,\n )\n\n if (!providerBlock?.voices) {\n return []\n }\n\n // Get enabled voices from provider block\n return providerBlock.voices\n .filter((v: Voice) => v.enabled !== false)\n .map((v: Voice) => ({\n label: v.name || v.id,\n value: v.id,\n }))\n }, [provider, aiSettings])\n\n // Clear voice selection when provider changes and current voice is not available\n useEffect(() => {\n if (value && voices.length > 0) {\n const voiceExists = voices.some((v) => v.value === value)\n if (!voiceExists) {\n setValue('')\n }\n }\n }, [voices, value, setValue])\n\n if (!provider) {\n return (\n <div className=\"field-type text\">\n <label className=\"field-label\" htmlFor={path}>\n Voice\n </label>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px' }}>\n Please select a provider first.\n </p>\n </div>\n )\n }\n\n if (isLoading) {\n return (\n <div className=\"field-type text\">\n <label className=\"field-label\" htmlFor={path}>\n Voice\n </label>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px' }}>Loading voices...</p>\n </div>\n )\n }\n\n if (voices.length === 0) {\n return (\n <div className=\"field-type text\">\n <label className=\"field-label\" htmlFor={path}>\n Voice\n </label>\n <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px' }}>\n No voices available. Please configure voices in AI Settings for {provider}.\n </p>\n </div>\n )\n }\n\n return (\n <div className=\"field-type select\">\n <label className=\"field-label\" htmlFor={path}>\n Voice\n </label>\n <SelectInput\n name={name}\n onChange={(option) => {\n if (option && typeof option === 'object' && 'value' in option) {\n setValue(option.value as string)\n } else {\n setValue(option)\n }\n }}\n options={voices}\n path={path}\n value={value}\n />\n </div>\n )\n}\n\n"],"names":["SelectInput","useField","useFormFields","React","useEffect","useMemo","useState","DynamicVoiceSelect","props","name","path","parentPath","split","slice","join","providerPath","providerField","fields","provider","value","setValue","aiSettings","setAiSettings","isLoading","setIsLoading","fetchSettings","response","fetch","ok","data","json","err","console","error","voices","providers","providerBlock","find","p","blockType","enabled","filter","v","map","label","id","length","voiceExists","some","div","className","htmlFor","style","color","fontSize","onChange","option","options"],"mappings":"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"}
@@ -5,34 +5,59 @@ export const DynamicVoiceSelect = (props) => {
5
5
  const { name, path } = props;
6
6
  // Get provider from siblings
7
7
  const parentPath = path.split('.').slice(0, -1).join('.');
8
- // Accessing provider from sibling data in a group
9
- const providerField = useFormFields(([fields]) => fields[`${parentPath}.provider`]);
10
- const provider = providerField?.value;
8
+ const providerPath = `${parentPath}.provider`;
9
+ // Use useFormFields to get the provider field value - this will re-render when provider changes
10
+ const providerField = useFormFields(([fields]) => fields[providerPath]);
11
+ const provider = providerField?.value || '';
11
12
  const { setValue, value } = useField({ path });
12
13
  const [aiSettings, setAiSettings] = useState(null);
14
+ const [isLoading, setIsLoading] = useState(true);
15
+ // Fetch AI Settings - re-fetch when provider changes to ensure we have latest voices
13
16
  useEffect(() => {
14
- fetch('/api/globals/ai-settings?depth=1')
15
- .then((res) => res.json())
16
- .then((data) => setAiSettings(data))
17
- .catch((err) => console.error('Error fetching AI settings:', err));
18
- }, []);
17
+ const fetchSettings = async () => {
18
+ setIsLoading(true);
19
+ try {
20
+ const response = await fetch('/api/globals/ai-settings?depth=1');
21
+ if (response.ok) {
22
+ const data = await response.json();
23
+ setAiSettings(data);
24
+ }
25
+ }
26
+ catch (err) {
27
+ console.error('Error fetching AI settings:', err);
28
+ }
29
+ finally {
30
+ setIsLoading(false);
31
+ }
32
+ };
33
+ void fetchSettings();
34
+ }, [provider]); // Re-fetch when provider changes to ensure we have the latest voices
19
35
  const voices = useMemo(() => {
20
- if (!provider || !aiSettings) {
36
+ if (!provider || !aiSettings?.providers) {
21
37
  return [];
22
38
  }
23
- const providerBlock = aiSettings.providers?.find((p) => p.blockType === provider && p.enabled);
24
- if (!providerBlock) {
39
+ // Find the provider block matching the selected provider
40
+ const providerBlock = aiSettings.providers.find((p) => p.blockType === provider && p.enabled !== false);
41
+ if (!providerBlock?.voices) {
25
42
  return [];
26
43
  }
27
- // Get voices from provider block
28
- const voicesArray = providerBlock.voices || [];
29
- return voicesArray
44
+ // Get enabled voices from provider block
45
+ return providerBlock.voices
30
46
  .filter((v) => v.enabled !== false)
31
47
  .map((v) => ({
32
48
  label: v.name || v.id,
33
49
  value: v.id,
34
50
  }));
35
51
  }, [provider, aiSettings]);
52
+ // Clear voice selection when provider changes and current voice is not available
53
+ useEffect(() => {
54
+ if (value && voices.length > 0) {
55
+ const voiceExists = voices.some((v) => v.value === value);
56
+ if (!voiceExists) {
57
+ setValue('');
58
+ }
59
+ }
60
+ }, [voices, value, setValue]);
36
61
  if (!provider) {
37
62
  return (<div className="field-type text">
38
63
  <label className="field-label" htmlFor={path}>
@@ -43,6 +68,14 @@ export const DynamicVoiceSelect = (props) => {
43
68
  </p>
44
69
  </div>);
45
70
  }
71
+ if (isLoading) {
72
+ return (<div className="field-type text">
73
+ <label className="field-label" htmlFor={path}>
74
+ Voice
75
+ </label>
76
+ <p style={{ color: 'var(--theme-elevation-600)', fontSize: '13px' }}>Loading voices...</p>
77
+ </div>);
78
+ }
46
79
  if (voices.length === 0) {
47
80
  return (<div className="field-type text">
48
81
  <label className="field-label" htmlFor={path}>