@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.
- package/dist/ai/core/media/image/handlers/multimodal.js +5 -0
- package/dist/ai/core/media/image/handlers/multimodal.js.map +1 -1
- package/dist/ai/core/streamObject.js +0 -3
- package/dist/ai/core/streamObject.js.map +1 -1
- package/dist/ai/providers/blocks/anthropic.js +2 -1
- package/dist/ai/providers/blocks/anthropic.js.map +1 -1
- package/dist/ai/providers/blocks/elevenlabs.js +2 -1
- package/dist/ai/providers/blocks/elevenlabs.js.map +1 -1
- package/dist/ai/providers/blocks/fal.js +2 -1
- package/dist/ai/providers/blocks/fal.js.map +1 -1
- package/dist/ai/providers/blocks/google.js +2 -1
- package/dist/ai/providers/blocks/google.js.map +1 -1
- package/dist/ai/providers/blocks/openai-compatible.js +2 -1
- package/dist/ai/providers/blocks/openai-compatible.js.map +1 -1
- package/dist/ai/providers/blocks/openai.js +2 -1
- package/dist/ai/providers/blocks/openai.js.map +1 -1
- package/dist/ai/providers/blocks/xai.js +2 -1
- package/dist/ai/providers/blocks/xai.js.map +1 -1
- package/dist/ai/providers/icons.d.ts +7 -0
- package/dist/ai/providers/icons.js +9 -0
- package/dist/ai/providers/icons.js.map +1 -0
- package/dist/ai/providers/registry.js +34 -23
- package/dist/ai/providers/registry.js.map +1 -1
- package/dist/collections/Instructions.js +37 -0
- package/dist/collections/Instructions.js.map +1 -1
- package/dist/endpoints/chat.d.ts +4 -0
- package/dist/endpoints/index.js +86 -10
- package/dist/endpoints/index.js.map +1 -1
- package/dist/exports/fields.d.ts +1 -0
- package/dist/exports/fields.js +1 -0
- package/dist/exports/fields.js.map +1 -1
- package/dist/fields/ArrayComposeField/ArrayComposeField.d.ts +15 -0
- package/dist/fields/ArrayComposeField/ArrayComposeField.js +87 -0
- package/dist/fields/ArrayComposeField/ArrayComposeField.js.map +1 -0
- package/dist/fields/ArrayComposeField/ArrayComposeField.jsx +73 -0
- package/dist/fields/PromptEditorField/PromptEditorField.js +7 -2
- package/dist/fields/PromptEditorField/PromptEditorField.js.map +1 -1
- package/dist/fields/PromptEditorField/PromptEditorField.jsx +5 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init.js +23 -6
- package/dist/init.js.map +1 -1
- package/dist/payload-ai.d.ts +149 -0
- package/dist/providers/InstructionsProvider/InstructionsProvider.js +3 -0
- package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
- package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +3 -0
- package/dist/providers/InstructionsProvider/useInstructions.js +18 -1
- package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
- package/dist/styles.d.ts +11 -0
- package/dist/types/handlebars-async-helpers.d.ts +1 -0
- package/dist/types/handlebars-dist-handlebars.d.ts +1 -0
- package/dist/types/react-mentions.d.ts +1 -0
- package/dist/ui/Compose/Compose.d.ts +1 -0
- package/dist/ui/Compose/Compose.js +2 -2
- package/dist/ui/Compose/Compose.js.map +1 -1
- package/dist/ui/Compose/Compose.jsx +2 -2
- package/dist/ui/Compose/UndoRedoActions.d.ts +2 -2
- package/dist/ui/Compose/UndoRedoActions.js +8 -5
- package/dist/ui/Compose/UndoRedoActions.js.map +1 -1
- package/dist/ui/Compose/UndoRedoActions.jsx +6 -5
- package/dist/ui/Compose/compose.module.css +56 -16
- package/dist/ui/Compose/hooks/menu/itemsMap.js +12 -6
- package/dist/ui/Compose/hooks/menu/itemsMap.js.map +1 -1
- package/dist/ui/Compose/hooks/menu/useMenu.js +26 -15
- package/dist/ui/Compose/hooks/menu/useMenu.js.map +1 -1
- package/dist/ui/Compose/hooks/menu/useMenu.jsx +25 -12
- package/dist/ui/Compose/hooks/useHistory.d.ts +0 -1
- package/dist/ui/Compose/hooks/useHistory.js +65 -25
- package/dist/ui/Compose/hooks/useHistory.js.map +1 -1
- package/dist/ui/DynamicVoiceSelect/index.js +63 -11
- package/dist/ui/DynamicVoiceSelect/index.js.map +1 -1
- package/dist/ui/DynamicVoiceSelect/index.jsx +47 -14
- package/dist/ui/VoicesFetcher/index.js +54 -8
- package/dist/ui/VoicesFetcher/index.js.map +1 -1
- package/dist/ui/VoicesFetcher/index.jsx +32 -9
- package/dist/utilities/buildSmartPrompt.d.ts +22 -0
- package/dist/utilities/buildSmartPrompt.js +143 -0
- package/dist/utilities/buildSmartPrompt.js.map +1 -0
- package/dist/utilities/resolveImageReferences.d.ts +3 -1
- package/dist/utilities/resolveImageReferences.js +21 -2
- package/dist/utilities/resolveImageReferences.js.map +1 -1
- package/dist/utilities/updateFieldsConfig.js +7 -1
- package/dist/utilities/updateFieldsConfig.js.map +1 -1
- package/package.json +3 -3
- package/dist/endpoints/chat.d.js +0 -3
- package/dist/endpoints/chat.d.js.map +0 -1
- package/dist/payload-ai.d.js +0 -3
- package/dist/payload-ai.d.js.map +0 -1
- package/dist/styles.d.js +0 -2
- package/dist/styles.d.js.map +0 -1
- package/dist/types/handlebars-async-helpers.d.js +0 -2
- package/dist/types/handlebars-async-helpers.d.js.map +0 -1
- package/dist/types/handlebars-dist-handlebars.d.js +0 -2
- package/dist/types/handlebars-dist-handlebars.d.js.map +0 -1
- package/dist/types/react-mentions.d.js +0 -2
- 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 {
|
|
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":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
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
|
|
22
|
-
const
|
|
22
|
+
const { field: { type: fieldType } = {}, path } = useFieldProps();
|
|
23
|
+
const { getData } = useForm();
|
|
23
24
|
const [activeComponent, setActiveComponent] = useState('Rephrase');
|
|
24
|
-
|
|
25
|
+
// Check value once on mount or when path/type changes
|
|
25
26
|
useEffect(() => {
|
|
26
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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,17 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useDocumentInfo,
|
|
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
|
|
12
|
-
const {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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 //
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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}>
|