@ai-stack/payloadcms 3.68.0-beta.2 → 3.68.0-beta.4
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/generateImage.js +2 -6
- package/dist/ai/core/media/image/generateImage.js.map +1 -1
- package/dist/ai/prompts.d.ts +1 -2
- package/dist/ai/prompts.js +0 -110
- package/dist/ai/prompts.js.map +1 -1
- package/dist/ai/providers/blocks/elevenlabs.js +1 -1
- package/dist/ai/providers/blocks/elevenlabs.js.map +1 -1
- package/dist/ai/providers/blocks/google.js +9 -5
- package/dist/ai/providers/blocks/google.js.map +1 -1
- package/dist/ai/providers/blocks/openai.js +1 -1
- package/dist/ai/providers/blocks/openai.js.map +1 -1
- package/dist/ai/providers/registry.js +0 -1
- package/dist/ai/providers/registry.js.map +1 -1
- package/dist/ai/utils/filterEditorSchemaByNodes.d.ts +9 -0
- package/dist/ai/utils/filterEditorSchemaByNodes.js +30 -3
- package/dist/ai/utils/filterEditorSchemaByNodes.js.map +1 -1
- package/dist/ai/utils/nodeToSchemaMap.d.ts +22 -0
- package/dist/ai/utils/nodeToSchemaMap.js +72 -0
- package/dist/ai/utils/nodeToSchemaMap.js.map +1 -0
- package/dist/collections/AISettings.js +44 -17
- package/dist/collections/AISettings.js.map +1 -1
- package/dist/defaults.d.ts +1 -0
- package/dist/defaults.js +8 -0
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/fetchFields.js +10 -0
- package/dist/endpoints/fetchFields.js.map +1 -1
- package/dist/endpoints/index.js +12 -0
- package/dist/endpoints/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +16 -32
- package/dist/plugin.js.map +1 -1
- package/dist/providers/InstructionsProvider/InstructionsProvider.js +44 -15
- package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
- package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +36 -16
- package/dist/providers/InstructionsProvider/context.d.ts +3 -0
- package/dist/providers/InstructionsProvider/context.js +2 -0
- package/dist/providers/InstructionsProvider/context.js.map +1 -1
- package/dist/providers/InstructionsProvider/useInstructions.js +3 -1
- package/dist/providers/InstructionsProvider/useInstructions.js.map +1 -1
- package/dist/types.d.ts +1 -5
- package/dist/types.js.map +1 -1
- package/dist/ui/AIConfigDashboard/index.js +198 -22
- package/dist/ui/AIConfigDashboard/index.js.map +1 -1
- package/dist/ui/AIConfigDashboard/index.jsx +159 -13
- package/dist/ui/Compose/Compose.js +21 -2
- package/dist/ui/Compose/Compose.js.map +1 -1
- package/dist/ui/Compose/Compose.jsx +21 -2
- package/dist/ui/Compose/hooks/useGenerate.js +26 -174
- package/dist/ui/Compose/hooks/useGenerate.js.map +1 -1
- package/dist/ui/Compose/hooks/useGenerateUpload.d.ts +11 -0
- package/dist/ui/Compose/hooks/useGenerateUpload.js +150 -0
- package/dist/ui/Compose/hooks/useGenerateUpload.js.map +1 -0
- package/dist/ui/Compose/hooks/useStreamingUpdate.d.ts +8 -0
- package/dist/ui/Compose/hooks/useStreamingUpdate.js +48 -0
- package/dist/ui/Compose/hooks/useStreamingUpdate.js.map +1 -0
- package/dist/ui/EncryptedTextField/index.js +4 -4
- package/dist/ui/EncryptedTextField/index.js.map +1 -1
- package/dist/ui/EncryptedTextField/index.jsx +4 -4
- package/dist/utilities/buildSmartPrompt.js +4 -6
- package/dist/utilities/buildSmartPrompt.js.map +1 -1
- package/dist/utilities/encryption.js +2 -1
- package/dist/utilities/encryption.js.map +1 -1
- package/dist/utilities/seedProperties.d.ts +7 -0
- package/dist/utilities/seedProperties.js +100 -0
- package/dist/utilities/seedProperties.js.map +1 -0
- package/dist/utilities/setSafeLexicalState.js +79 -6
- package/dist/utilities/setSafeLexicalState.js.map +1 -1
- package/dist/utilities/updateFieldsConfig.d.ts +1 -1
- package/dist/utilities/updateFieldsConfig.js +1 -0
- package/dist/utilities/updateFieldsConfig.js.map +1 -1
- package/package.json +2 -1
- package/dist/init.d.ts +0 -7
- package/dist/init.js +0 -152
- package/dist/init.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/AIConfigDashboard/index.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\n\nexport const AIConfigDashboard: React.FC = () => {\n return (\n <div\n style={{\n alignItems: 'center',\n background: 'var(--theme-elevation-50)',\n border: '1px solid var(--theme-elevation-150)',\n borderRadius: '8px',\n display: 'flex',\n justifyContent: 'space-between',\n marginBottom: '20px',\n padding: '20px',\n }}\n >\n <div>\n <h4 style={{ margin: '0 0 5px 0' }}>AI Configuration</h4>\n <p style={{ color: 'var(--theme-elevation-500)', fontSize: '14px', margin: '0' }}>\n Manage your AI providers, API keys, and default models.\n </p>\n </div>\n <a href=\"/admin/globals/ai-settings\">\n <button className=\"btn btn--style-primary btn--size-small\">Manage AI Settings</button>\n </a>\n </div>\n )\n}\n"],"names":["React","AIConfigDashboard","div","style","alignItems","background","border","borderRadius","display","justifyContent","marginBottom","padding","h4","margin","p","color","fontSize","a","href","button","className"],"mappings":"AAAA;;AAEA,OAAOA,WAAW,QAAO;AAEzB,OAAO,MAAMC,oBAA8B;IACzC,qBACE,MAACC;QACCC,OAAO;YACLC,YAAY;YACZC,YAAY;YACZC,QAAQ;YACRC,cAAc;YACdC,SAAS;YACTC,gBAAgB;YAChBC,cAAc;YACdC,SAAS;QACX;;0BAEA,MAACT;;kCACC,KAACU;wBAAGT,OAAO;4BAAEU,QAAQ;wBAAY;kCAAG;;kCACpC,KAACC;wBAAEX,OAAO;4BAAEY,OAAO;4BAA8BC,UAAU;4BAAQH,QAAQ;wBAAI;kCAAG;;;;0BAIpF,KAACI;gBAAEC,MAAK;0BACN,cAAA,KAACC;oBAAOC,WAAU;8BAAyC;;;;;AAInE,EAAC"}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/AIConfigDashboard/index.tsx"],"sourcesContent":["'use client'\n\nimport { Button, toast, useConfig } from '@payloadcms/ui'\n// @ts-expect-error - Next.js types are not resolving correctly with nodenext but runtime is fine\nimport { useRouter } from 'next/navigation'\nimport React, { useContext, useEffect, useState } from 'react'\n\nimport { excludeCollections } from '../../defaults.js'\nimport { InstructionsContext } from '../../providers/InstructionsProvider/context.js'\n\nexport const AIConfigDashboard: React.FC = () => {\n const {\n config: {\n collections,\n routes: { admin: adminRoute, api: apiRoute },\n },\n } = useConfig()\n const router = useRouter()\n const { refresh, setEnabledCollections: setEnabledCollectionsInContext } =\n useContext(InstructionsContext)\n\n const [enabledCollections, setEnabledCollections] = useState<string[]>([])\n const [isLoading, setIsLoading] = useState(true)\n const [isSaving, setIsSaving] = useState(false)\n\n const availableCollections = collections.filter(\n (c) =>\n !excludeCollections.includes(c.slug) &&\n !(c.admin as unknown as { hidden?: ((args: any) => boolean) | boolean })?.hidden,\n )\n\n useEffect(() => {\n const fetchSettings = async () => {\n try {\n const response = await fetch(`${apiRoute}/globals/ai-settings`)\n if (response.ok) {\n const data = await response.json()\n // Handle both simple array and object wrapper if Payload wraps it\n const storedEnabled = data.enabledCollections || []\n setEnabledCollections(Array.isArray(storedEnabled) ? storedEnabled : [])\n }\n } catch (error) {\n console.error('Failed to fetch AI settings:', error)\n } finally {\n setIsLoading(false)\n }\n }\n\n fetchSettings().catch((e) => {\n console.log(e)\n })\n }, [apiRoute])\n\n const handleToggle = (slug: string) => {\n setEnabledCollections((prev) => {\n if (prev.includes(slug)) {\n return prev.filter((s) => s !== slug)\n }\n return [...prev, slug]\n })\n }\n\n const handleSave = async () => {\n setIsSaving(true)\n try {\n // First fetch current settings to get ID or just rely on global update behavior\n // We need to adhere to Payload's global update API\n const response = await fetch(`${apiRoute}/globals/ai-settings`, {\n body: JSON.stringify({\n enabledCollections,\n }),\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (response.ok) {\n toast.success('Settings saved successfully')\n if (setEnabledCollectionsInContext) {\n setEnabledCollectionsInContext(enabledCollections)\n }\n if (refresh) {\n await refresh()\n }\n router.refresh()\n } else {\n toast.error('Failed to save settings')\n }\n } catch (error) {\n console.error('Error saving settings:', error)\n toast.error('Error saving settings')\n } finally {\n setIsSaving(false)\n }\n }\n\n if (isLoading) {\n return <div style={{ padding: '20px', textAlign: 'center' }}>Loading AI configuration...</div>\n }\n\n return (\n <div\n style={{\n background: 'var(--theme-elevation-50)',\n // border: '1px solid var(--theme-elevation-150)',\n // borderRadius: '8px',\n marginBottom: '20px',\n overflow: 'hidden',\n }}\n >\n <div\n style={{\n alignItems: 'center',\n borderBottom: '1px solid var(--theme-elevation-150)',\n display: 'flex',\n justifyContent: 'space-between',\n padding: '20px',\n }}\n >\n <div>\n <h4 style={{ margin: '0 0 5px 0' }}>AI Configuration</h4>\n <p style={{ color: 'var(--theme-elevation-500)', fontSize: '14px', margin: '0' }}>\n Manage your AI providers, API keys, and enable AI for specific collections.\n </p>\n </div>\n <div style={{ display: 'flex', gap: '10px' }}>\n <Button buttonStyle=\"secondary\" el=\"link\" to={`${adminRoute}/globals/ai-settings`}>\n Settings\n </Button>\n <Button\n disabled={isSaving}\n onClick={handleSave}\n >\n {isSaving ? 'Saving...' : 'Save Changes'}\n </Button>\n </div>\n </div>\n\n <div style={{ padding: '20px' }}>\n <h5 style={{ marginBottom: '15px' }}>Enabled Collections</h5>\n <div\n style={{\n display: 'grid',\n gap: '15px',\n gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',\n }}\n >\n {availableCollections.map((collection) => {\n const isEnabled = enabledCollections.includes(collection.slug)\n return (\n <button\n key={collection.slug}\n onClick={() => handleToggle(collection.slug)}\n style={{\n alignItems: 'center',\n background: isEnabled\n ? 'var(--theme-elevation-100)'\n : 'var(--theme-elevation-50)',\n border: `1px solid ${isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)'}`,\n borderRadius: '6px',\n cursor: 'pointer',\n display: 'flex',\n gap: '10px',\n padding: '10px 15px',\n textAlign: 'left',\n transition: 'all 0.2s ease',\n width: '100%',\n }}\n type=\"button\"\n >\n <div\n style={{\n alignItems: 'center',\n background: isEnabled\n ? 'var(--theme-text-success)'\n : 'var(--theme-elevation-200)',\n borderRadius: '12px',\n display: 'flex',\n height: '24px',\n justifyContent: isEnabled ? 'flex-end' : 'flex-start',\n padding: '2px',\n transition: 'all 0.2s ease',\n width: '44px',\n }}\n >\n <div\n style={{\n background: 'white',\n borderRadius: '50%',\n height: '20px',\n width: '20px',\n }}\n />\n </div>\n <span style={{ fontWeight: 500 }}>\n {typeof collection.labels?.singular === 'string'\n ? collection.labels.singular\n : collection.labels?.singular?.en || collection.slug}\n </span>\n </button>\n )\n })}\n </div>\n </div>\n </div>\n )\n}\n"],"names":["Button","toast","useConfig","useRouter","React","useContext","useEffect","useState","excludeCollections","InstructionsContext","AIConfigDashboard","config","collections","routes","admin","adminRoute","api","apiRoute","router","refresh","setEnabledCollections","setEnabledCollectionsInContext","enabledCollections","isLoading","setIsLoading","isSaving","setIsSaving","availableCollections","filter","c","includes","slug","hidden","fetchSettings","response","fetch","ok","data","json","storedEnabled","Array","isArray","error","console","catch","e","log","handleToggle","prev","s","handleSave","body","JSON","stringify","headers","method","success","div","style","padding","textAlign","background","marginBottom","overflow","alignItems","borderBottom","display","justifyContent","h4","margin","p","color","fontSize","gap","buttonStyle","el","to","disabled","onClick","h5","gridTemplateColumns","map","collection","isEnabled","button","border","borderRadius","cursor","transition","width","type","height","span","fontWeight","labels","singular","en"],"mappings":"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"}
|
|
@@ -1,24 +1,170 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import
|
|
2
|
+
import { Button, toast, useConfig } from '@payloadcms/ui';
|
|
3
|
+
// @ts-expect-error - Next.js types are not resolving correctly with nodenext but runtime is fine
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
6
|
+
import { excludeCollections } from '../../defaults.js';
|
|
7
|
+
import { InstructionsContext } from '../../providers/InstructionsProvider/context.js';
|
|
3
8
|
export const AIConfigDashboard = () => {
|
|
9
|
+
const { config: { collections, routes: { admin: adminRoute, api: apiRoute }, }, } = useConfig();
|
|
10
|
+
const router = useRouter();
|
|
11
|
+
const { refresh, setEnabledCollections: setEnabledCollectionsInContext } = useContext(InstructionsContext);
|
|
12
|
+
const [enabledCollections, setEnabledCollections] = useState([]);
|
|
13
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
14
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
15
|
+
const availableCollections = collections.filter((c) => !excludeCollections.includes(c.slug) &&
|
|
16
|
+
!c.admin?.hidden);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const fetchSettings = async () => {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(`${apiRoute}/globals/ai-settings`);
|
|
21
|
+
if (response.ok) {
|
|
22
|
+
const data = await response.json();
|
|
23
|
+
// Handle both simple array and object wrapper if Payload wraps it
|
|
24
|
+
const storedEnabled = data.enabledCollections || [];
|
|
25
|
+
setEnabledCollections(Array.isArray(storedEnabled) ? storedEnabled : []);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error('Failed to fetch AI settings:', error);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
setIsLoading(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
fetchSettings().catch((e) => {
|
|
36
|
+
console.log(e);
|
|
37
|
+
});
|
|
38
|
+
}, [apiRoute]);
|
|
39
|
+
const handleToggle = (slug) => {
|
|
40
|
+
setEnabledCollections((prev) => {
|
|
41
|
+
if (prev.includes(slug)) {
|
|
42
|
+
return prev.filter((s) => s !== slug);
|
|
43
|
+
}
|
|
44
|
+
return [...prev, slug];
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
const handleSave = async () => {
|
|
48
|
+
setIsSaving(true);
|
|
49
|
+
try {
|
|
50
|
+
// First fetch current settings to get ID or just rely on global update behavior
|
|
51
|
+
// We need to adhere to Payload's global update API
|
|
52
|
+
const response = await fetch(`${apiRoute}/globals/ai-settings`, {
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
enabledCollections,
|
|
55
|
+
}),
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
},
|
|
59
|
+
method: 'POST',
|
|
60
|
+
});
|
|
61
|
+
if (response.ok) {
|
|
62
|
+
toast.success('Settings saved successfully');
|
|
63
|
+
if (setEnabledCollectionsInContext) {
|
|
64
|
+
setEnabledCollectionsInContext(enabledCollections);
|
|
65
|
+
}
|
|
66
|
+
if (refresh) {
|
|
67
|
+
await refresh();
|
|
68
|
+
}
|
|
69
|
+
router.refresh();
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
toast.error('Failed to save settings');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
console.error('Error saving settings:', error);
|
|
77
|
+
toast.error('Error saving settings');
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
setIsSaving(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
if (isLoading) {
|
|
84
|
+
return <div style={{ padding: '20px', textAlign: 'center' }}>Loading AI configuration...</div>;
|
|
85
|
+
}
|
|
4
86
|
return (<div style={{
|
|
5
|
-
alignItems: 'center',
|
|
6
87
|
background: 'var(--theme-elevation-50)',
|
|
7
|
-
border: '1px solid var(--theme-elevation-150)',
|
|
8
|
-
borderRadius: '8px',
|
|
88
|
+
// border: '1px solid var(--theme-elevation-150)',
|
|
89
|
+
// borderRadius: '8px',
|
|
90
|
+
marginBottom: '20px',
|
|
91
|
+
overflow: 'hidden',
|
|
92
|
+
}}>
|
|
93
|
+
<div style={{
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
borderBottom: '1px solid var(--theme-elevation-150)',
|
|
9
96
|
display: 'flex',
|
|
10
97
|
justifyContent: 'space-between',
|
|
11
|
-
marginBottom: '20px',
|
|
12
98
|
padding: '20px',
|
|
13
99
|
}}>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
100
|
+
<div>
|
|
101
|
+
<h4 style={{ margin: '0 0 5px 0' }}>AI Configuration</h4>
|
|
102
|
+
<p style={{ color: 'var(--theme-elevation-500)', fontSize: '14px', margin: '0' }}>
|
|
103
|
+
Manage your AI providers, API keys, and enable AI for specific collections.
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
<div style={{ display: 'flex', gap: '10px' }}>
|
|
107
|
+
<Button buttonStyle="secondary" el="link" to={`${adminRoute}/globals/ai-settings`}>
|
|
108
|
+
Settings
|
|
109
|
+
</Button>
|
|
110
|
+
<Button disabled={isSaving} onClick={handleSave}>
|
|
111
|
+
{isSaving ? 'Saving...' : 'Save Changes'}
|
|
112
|
+
</Button>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div style={{ padding: '20px' }}>
|
|
117
|
+
<h5 style={{ marginBottom: '15px' }}>Enabled Collections</h5>
|
|
118
|
+
<div style={{
|
|
119
|
+
display: 'grid',
|
|
120
|
+
gap: '15px',
|
|
121
|
+
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
|
|
122
|
+
}}>
|
|
123
|
+
{availableCollections.map((collection) => {
|
|
124
|
+
const isEnabled = enabledCollections.includes(collection.slug);
|
|
125
|
+
return (<button key={collection.slug} onClick={() => handleToggle(collection.slug)} style={{
|
|
126
|
+
alignItems: 'center',
|
|
127
|
+
background: isEnabled
|
|
128
|
+
? 'var(--theme-elevation-100)'
|
|
129
|
+
: 'var(--theme-elevation-50)',
|
|
130
|
+
border: `1px solid ${isEnabled ? 'var(--theme-text-success)' : 'var(--theme-elevation-200)'}`,
|
|
131
|
+
borderRadius: '6px',
|
|
132
|
+
cursor: 'pointer',
|
|
133
|
+
display: 'flex',
|
|
134
|
+
gap: '10px',
|
|
135
|
+
padding: '10px 15px',
|
|
136
|
+
textAlign: 'left',
|
|
137
|
+
transition: 'all 0.2s ease',
|
|
138
|
+
width: '100%',
|
|
139
|
+
}} type="button">
|
|
140
|
+
<div style={{
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
background: isEnabled
|
|
143
|
+
? 'var(--theme-text-success)'
|
|
144
|
+
: 'var(--theme-elevation-200)',
|
|
145
|
+
borderRadius: '12px',
|
|
146
|
+
display: 'flex',
|
|
147
|
+
height: '24px',
|
|
148
|
+
justifyContent: isEnabled ? 'flex-end' : 'flex-start',
|
|
149
|
+
padding: '2px',
|
|
150
|
+
transition: 'all 0.2s ease',
|
|
151
|
+
width: '44px',
|
|
152
|
+
}}>
|
|
153
|
+
<div style={{
|
|
154
|
+
background: 'white',
|
|
155
|
+
borderRadius: '50%',
|
|
156
|
+
height: '20px',
|
|
157
|
+
width: '20px',
|
|
158
|
+
}}/>
|
|
159
|
+
</div>
|
|
160
|
+
<span style={{ fontWeight: 500 }}>
|
|
161
|
+
{typeof collection.labels?.singular === 'string'
|
|
162
|
+
? collection.labels.singular
|
|
163
|
+
: collection.labels?.singular?.en || collection.slug}
|
|
164
|
+
</span>
|
|
165
|
+
</button>);
|
|
166
|
+
})}
|
|
167
|
+
</div>
|
|
19
168
|
</div>
|
|
20
|
-
<a href="/admin/globals/ai-settings">
|
|
21
|
-
<button className="btn btn--style-primary btn--size-small">Manage AI Settings</button>
|
|
22
|
-
</a>
|
|
23
169
|
</div>);
|
|
24
170
|
};
|
|
@@ -134,9 +134,28 @@ export const Compose = ({ descriptionProps, forceVisible, instructionId, isConfi
|
|
|
134
134
|
path: pathFromContext
|
|
135
135
|
});
|
|
136
136
|
const setIfValueIsLexicalState = useCallback((val)=>{
|
|
137
|
-
|
|
138
|
-
|
|
137
|
+
// Prevent setting incomplete states during streaming
|
|
138
|
+
if (!val || typeof val !== 'object' || !('root' in val) || !lexicalEditor) {
|
|
139
|
+
return;
|
|
139
140
|
}
|
|
141
|
+
// Validate that the state is complete before setting
|
|
142
|
+
// Check for common incomplete streaming states
|
|
143
|
+
if (!val.root || typeof val.root !== 'object' || Object.keys(val.root).length === 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (val.root.type !== 'root') {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (!val.root.children || !Array.isArray(val.root.children) || val.root.children.length === 0) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
// Check for invalid child types (common streaming issue)
|
|
153
|
+
const hasInvalidChildren = val.root.children.some((child)=>!child || !child.type || child.type === 'undefined' || child.type === '');
|
|
154
|
+
if (hasInvalidChildren) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// State looks valid, proceed
|
|
158
|
+
setSafeLexicalState(JSON.stringify(val), lexicalEditor);
|
|
140
159
|
// DO NOT PROVIDE lexicalEditor as a dependency, it freaks out and does not update the editor after first undo/redo - revisit
|
|
141
160
|
}, []);
|
|
142
161
|
const popupRender = useCallback(({ close })=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/Compose/Compose.tsx"],"sourcesContent":["'use client'\n\nimport type { ClientField } from 'payload'\nimport type { FC } from 'react'\n\nimport { useEditorConfigContext } from '@payloadcms/richtext-lexical/client'\nimport { Popup, useField } from '@payloadcms/ui'\nimport React, { useCallback, useMemo, useState } from 'react'\n\nimport { PLUGIN_INSTRUCTIONS_TABLE } from '../../defaults.js'\nimport { useInstructions } from '../../providers/InstructionsProvider/useInstructions.js'\nimport { setSafeLexicalState } from '../../utilities/setSafeLexicalState.js'\nimport { PluginIcon } from '../Icons/Icons.js'\nimport styles from './compose.module.css'\nimport { useMenu } from './hooks/menu/useMenu.js'\nimport { useActiveFieldTracking } from './hooks/useActiveFieldTracking.js'\nimport { useGenerate } from './hooks/useGenerate.js'\nimport { UndoRedoActions } from './UndoRedoActions.js'\n\nexport type ComposeProps = {\n descriptionProps?: {\n field: ClientField\n path: string\n schemaPath: string\n }\n forceVisible?: boolean\n instructionId: string\n isConfigAllowed: boolean\n}\n\nexport const Compose: FC<ComposeProps> = ({ descriptionProps, forceVisible, instructionId, isConfigAllowed }) => {\n const pathFromContext = descriptionProps?.path\n const { editor: lexicalEditor } = useEditorConfigContext()\n \n // Get global openDrawer from context\n const { openDrawer } = useInstructions()\n\n // Initialize global active-field tracking\n useActiveFieldTracking()\n\n const [isProcessing, setIsProcessing] = useState<boolean>(false)\n const { generate, isJobActive, isLoading, jobProgress, jobStatus, stop } = useGenerate({ instructionId })\n\n // Memoize menu event handlers to prevent recreation on every render\n const onCompose = useCallback(() => {\n console.log('Composing...')\n setIsProcessing(true)\n generate({\n action: 'Compose',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onExpand = useCallback(() => {\n console.log('Expanding...')\n generate({\n action: 'Expand',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onProofread = useCallback(() => {\n console.log('Proofreading...')\n generate({\n action: 'Proofread',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onRephrase = useCallback(() => {\n console.log('Rephrasing...')\n generate({\n action: 'Rephrase',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onSimplify = useCallback(() => {\n console.log('Simplifying...')\n generate({\n action: 'Simplify',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onSummarize = useCallback(() => {\n console.log('Summarizing...')\n generate({\n action: 'Summarize',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onTranslate = useCallback((data: unknown) => {\n console.log('Translating...')\n generate({\n action: 'Translate',\n params: data,\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const handleOpenSettings = useCallback(() => {\n if (isConfigAllowed) {\n openDrawer(instructionId)\n }\n }, [isConfigAllowed, openDrawer, instructionId])\n\n const { ActiveComponent, Menu } = useMenu(\n {\n onCompose,\n onExpand,\n onProofread,\n onRephrase,\n onSettings: isConfigAllowed ? handleOpenSettings : undefined,\n onSimplify,\n onSummarize,\n onTranslate,\n },\n {\n isConfigAllowed,\n },\n )\n\n const { setValue } = useField<string>({\n path: pathFromContext,\n })\n\n const setIfValueIsLexicalState = useCallback((val: any) => {\n if (val && typeof val === 'object' && 'root' in val && lexicalEditor) {\n setSafeLexicalState(JSON.stringify(val), lexicalEditor)\n }\n\n // DO NOT PROVIDE lexicalEditor as a dependency, it freaks out and does not update the editor after first undo/redo - revisit\n }, [])\n\n const popupRender = useCallback(\n ({ close }: { close: () => void }) => {\n return <Menu isLoading={isProcessing || isLoading} onClose={close} />\n },\n [isProcessing, isLoading, Menu],\n )\n\n // Combine loading states to reduce re-renders\n const isAnyLoading = isProcessing || isLoading || isJobActive\n\n const memoizedPopup = useMemo(() => {\n return (\n <Popup\n button={<PluginIcon isLoading={isAnyLoading} />}\n render={popupRender}\n verticalAlign=\"bottom\"\n />\n )\n }, [popupRender, isAnyLoading])\n\n return (\n <label\n className={`payloadai-compose__actions ${styles.actions} ${forceVisible ? styles.actionsVisible : ''}`}\n onClick={(e) => e.preventDefault()}\n role=\"presentation\"\n >\n {memoizedPopup}\n <ActiveComponent\n isLoading={isProcessing || isLoading || isJobActive}\n loadingLabel={isJobActive ? (jobStatus === 'running' ? `Video ${Math.max(0, Math.min(100, Math.round(jobProgress ?? 0)))}%` : (jobStatus || 'Queued')) : undefined}\n stop={stop}\n />\n <UndoRedoActions\n onChange={(val) => {\n setValue(val)\n setIfValueIsLexicalState(val)\n }}\n />\n </label>\n )\n}\n"],"names":["useEditorConfigContext","Popup","useField","React","useCallback","useMemo","useState","useInstructions","setSafeLexicalState","PluginIcon","styles","useMenu","useActiveFieldTracking","useGenerate","UndoRedoActions","Compose","descriptionProps","forceVisible","instructionId","isConfigAllowed","pathFromContext","path","editor","lexicalEditor","openDrawer","isProcessing","setIsProcessing","generate","isJobActive","isLoading","jobProgress","jobStatus","stop","onCompose","console","log","action","catch","reason","error","finally","onExpand","onProofread","onRephrase","onSimplify","onSummarize","onTranslate","data","params","handleOpenSettings","ActiveComponent","Menu","onSettings","undefined","setValue","setIfValueIsLexicalState","val","JSON","stringify","popupRender","close","onClose","isAnyLoading","memoizedPopup","button","render","verticalAlign","label","className","actions","actionsVisible","onClick","e","preventDefault","role","loadingLabel","Math","max","min","round","onChange"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/Compose/Compose.tsx"],"sourcesContent":["'use client'\n\nimport type { ClientField } from 'payload'\nimport type { FC } from 'react'\n\nimport { useEditorConfigContext } from '@payloadcms/richtext-lexical/client'\nimport { Popup, useField } from '@payloadcms/ui'\nimport React, { useCallback, useMemo, useState } from 'react'\n\nimport { PLUGIN_INSTRUCTIONS_TABLE } from '../../defaults.js'\nimport { useInstructions } from '../../providers/InstructionsProvider/useInstructions.js'\nimport { setSafeLexicalState } from '../../utilities/setSafeLexicalState.js'\nimport { PluginIcon } from '../Icons/Icons.js'\nimport styles from './compose.module.css'\nimport { useMenu } from './hooks/menu/useMenu.js'\nimport { useActiveFieldTracking } from './hooks/useActiveFieldTracking.js'\nimport { useGenerate } from './hooks/useGenerate.js'\nimport { UndoRedoActions } from './UndoRedoActions.js'\n\nexport type ComposeProps = {\n descriptionProps?: {\n field: ClientField\n path: string\n schemaPath: string\n }\n forceVisible?: boolean\n instructionId: string\n isConfigAllowed: boolean\n}\n\nexport const Compose: FC<ComposeProps> = ({ descriptionProps, forceVisible, instructionId, isConfigAllowed }) => {\n const pathFromContext = descriptionProps?.path\n const { editor: lexicalEditor } = useEditorConfigContext()\n \n // Get global openDrawer from context\n const { openDrawer } = useInstructions()\n\n // Initialize global active-field tracking\n useActiveFieldTracking()\n\n const [isProcessing, setIsProcessing] = useState<boolean>(false)\n const { generate, isJobActive, isLoading, jobProgress, jobStatus, stop } = useGenerate({ instructionId })\n\n // Memoize menu event handlers to prevent recreation on every render\n const onCompose = useCallback(() => {\n console.log('Composing...')\n setIsProcessing(true)\n generate({\n action: 'Compose',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onExpand = useCallback(() => {\n console.log('Expanding...')\n generate({\n action: 'Expand',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onProofread = useCallback(() => {\n console.log('Proofreading...')\n generate({\n action: 'Proofread',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onRephrase = useCallback(() => {\n console.log('Rephrasing...')\n generate({\n action: 'Rephrase',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onSimplify = useCallback(() => {\n console.log('Simplifying...')\n generate({\n action: 'Simplify',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onSummarize = useCallback(() => {\n console.log('Summarizing...')\n generate({\n action: 'Summarize',\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const onTranslate = useCallback((data: unknown) => {\n console.log('Translating...')\n generate({\n action: 'Translate',\n params: data,\n })\n .catch((reason) => {\n console.error('Compose : ', reason)\n })\n .finally(() => {\n setIsProcessing(false)\n })\n }, [generate])\n\n const handleOpenSettings = useCallback(() => {\n if (isConfigAllowed) {\n openDrawer(instructionId)\n }\n }, [isConfigAllowed, openDrawer, instructionId])\n\n const { ActiveComponent, Menu } = useMenu(\n {\n onCompose,\n onExpand,\n onProofread,\n onRephrase,\n onSettings: isConfigAllowed ? handleOpenSettings : undefined,\n onSimplify,\n onSummarize,\n onTranslate,\n },\n {\n isConfigAllowed,\n },\n )\n\n const { setValue } = useField<string>({\n path: pathFromContext,\n })\n\n const setIfValueIsLexicalState = useCallback((val: any) => {\n // Prevent setting incomplete states during streaming\n if (!val || typeof val !== 'object' || !('root' in val) || !lexicalEditor) {\n return\n }\n\n // Validate that the state is complete before setting\n // Check for common incomplete streaming states\n if (!val.root || typeof val.root !== 'object' || Object.keys(val.root).length === 0) {\n return\n }\n\n if (val.root.type !== 'root') {\n return\n }\n\n if (!val.root.children || !Array.isArray(val.root.children) || val.root.children.length === 0) {\n return\n }\n\n // Check for invalid child types (common streaming issue)\n const hasInvalidChildren = val.root.children.some(\n (child: any) => !child || !child.type || child.type === 'undefined' || child.type === '',\n )\n\n if (hasInvalidChildren) {\n return\n }\n\n // State looks valid, proceed\n setSafeLexicalState(JSON.stringify(val), lexicalEditor)\n\n // DO NOT PROVIDE lexicalEditor as a dependency, it freaks out and does not update the editor after first undo/redo - revisit\n }, [])\n\n const popupRender = useCallback(\n ({ close }: { close: () => void }) => {\n return <Menu isLoading={isProcessing || isLoading} onClose={close} />\n },\n [isProcessing, isLoading, Menu],\n )\n\n // Combine loading states to reduce re-renders\n const isAnyLoading = isProcessing || isLoading || isJobActive\n\n const memoizedPopup = useMemo(() => {\n return (\n <Popup\n button={<PluginIcon isLoading={isAnyLoading} />}\n render={popupRender}\n verticalAlign=\"bottom\"\n />\n )\n }, [popupRender, isAnyLoading])\n\n return (\n <label\n className={`payloadai-compose__actions ${styles.actions} ${forceVisible ? styles.actionsVisible : ''}`}\n onClick={(e) => e.preventDefault()}\n role=\"presentation\"\n >\n {memoizedPopup}\n <ActiveComponent\n isLoading={isProcessing || isLoading || isJobActive}\n loadingLabel={isJobActive ? (jobStatus === 'running' ? `Video ${Math.max(0, Math.min(100, Math.round(jobProgress ?? 0)))}%` : (jobStatus || 'Queued')) : undefined}\n stop={stop}\n />\n <UndoRedoActions\n onChange={(val) => {\n setValue(val)\n setIfValueIsLexicalState(val)\n }}\n />\n </label>\n )\n}\n"],"names":["useEditorConfigContext","Popup","useField","React","useCallback","useMemo","useState","useInstructions","setSafeLexicalState","PluginIcon","styles","useMenu","useActiveFieldTracking","useGenerate","UndoRedoActions","Compose","descriptionProps","forceVisible","instructionId","isConfigAllowed","pathFromContext","path","editor","lexicalEditor","openDrawer","isProcessing","setIsProcessing","generate","isJobActive","isLoading","jobProgress","jobStatus","stop","onCompose","console","log","action","catch","reason","error","finally","onExpand","onProofread","onRephrase","onSimplify","onSummarize","onTranslate","data","params","handleOpenSettings","ActiveComponent","Menu","onSettings","undefined","setValue","setIfValueIsLexicalState","val","root","Object","keys","length","type","children","Array","isArray","hasInvalidChildren","some","child","JSON","stringify","popupRender","close","onClose","isAnyLoading","memoizedPopup","button","render","verticalAlign","label","className","actions","actionsVisible","onClick","e","preventDefault","role","loadingLabel","Math","max","min","round","onChange"],"mappings":"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"}
|
|
@@ -127,9 +127,28 @@ export const Compose = ({ descriptionProps, forceVisible, instructionId, isConfi
|
|
|
127
127
|
path: pathFromContext,
|
|
128
128
|
});
|
|
129
129
|
const setIfValueIsLexicalState = useCallback((val) => {
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
// Prevent setting incomplete states during streaming
|
|
131
|
+
if (!val || typeof val !== 'object' || !('root' in val) || !lexicalEditor) {
|
|
132
|
+
return;
|
|
132
133
|
}
|
|
134
|
+
// Validate that the state is complete before setting
|
|
135
|
+
// Check for common incomplete streaming states
|
|
136
|
+
if (!val.root || typeof val.root !== 'object' || Object.keys(val.root).length === 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (val.root.type !== 'root') {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (!val.root.children || !Array.isArray(val.root.children) || val.root.children.length === 0) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Check for invalid child types (common streaming issue)
|
|
146
|
+
const hasInvalidChildren = val.root.children.some((child) => !child || !child.type || child.type === 'undefined' || child.type === '');
|
|
147
|
+
if (hasInvalidChildren) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// State looks valid, proceed
|
|
151
|
+
setSafeLexicalState(JSON.stringify(val), lexicalEditor);
|
|
133
152
|
// DO NOT PROVIDE lexicalEditor as a dependency, it freaks out and does not update the editor after first undo/redo - revisit
|
|
134
153
|
}, []);
|
|
135
154
|
const popupRender = useCallback(({ close }) => {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { experimental_useObject as useObject } from '@ai-sdk/react';
|
|
2
2
|
import { useEditorConfigContext } from '@payloadcms/richtext-lexical/client';
|
|
3
|
-
import { toast,
|
|
3
|
+
import { toast, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui';
|
|
4
4
|
import { jsonSchema } from 'ai';
|
|
5
|
-
import { useCallback, useEffect,
|
|
6
|
-
import {
|
|
5
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
6
|
+
import { PLUGIN_API_ENDPOINT_GENERATE } from '../../../defaults.js';
|
|
7
7
|
import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js';
|
|
8
|
-
import { editorSchemaValidator } from '../../../utilities/editorSchemaValidator.js';
|
|
9
|
-
import { fieldToJsonSchema } from '../../../utilities/fieldToJsonSchema.js';
|
|
10
8
|
import { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js';
|
|
9
|
+
import { useGenerateUpload } from './useGenerateUpload.js';
|
|
11
10
|
import { useHistory } from './useHistory.js';
|
|
11
|
+
import { useStreamingUpdate } from './useStreamingUpdate.js';
|
|
12
12
|
export const useGenerate = ({ instructionId })=>{
|
|
13
13
|
// Create a ref to hold the current instructionId
|
|
14
14
|
const instructionIdRef = useRef(instructionId);
|
|
@@ -20,66 +20,13 @@ export const useGenerate = ({ instructionId })=>{
|
|
|
20
20
|
]);
|
|
21
21
|
const { field, path: pathFromContext } = useFieldProps();
|
|
22
22
|
const editorConfigContext = useEditorConfigContext();
|
|
23
|
-
const { editor } = editorConfigContext;
|
|
24
|
-
const { config } = useConfig();
|
|
25
|
-
const { routes: { api }, serverURL } = config;
|
|
26
23
|
const { setValue } = useField({
|
|
27
24
|
path: pathFromContext ?? ''
|
|
28
25
|
});
|
|
29
26
|
const { set: setHistory } = useHistory();
|
|
30
|
-
// Async job UI state
|
|
31
|
-
const [jobStatus, setJobStatus] = useState(undefined);
|
|
32
|
-
const [jobProgress, setJobProgress] = useState(0);
|
|
33
|
-
const [isJobActive, setIsJobActive] = useState(false);
|
|
34
27
|
const { getData } = useForm();
|
|
35
|
-
const { id: documentId
|
|
28
|
+
const { id: documentId } = useDocumentInfo();
|
|
36
29
|
const localFromContext = useLocale();
|
|
37
|
-
// Reuse config from above instead of calling useConfig again
|
|
38
|
-
const { collections } = config;
|
|
39
|
-
const collection = collections.find((collection)=>collection.slug === PLUGIN_INSTRUCTIONS_TABLE);
|
|
40
|
-
const { custom: { [PLUGIN_NAME]: { editorConfig = {} } = {} } = {} } = collection?.admin ?? {};
|
|
41
|
-
const { schema: editorSchema = {} } = editorConfig;
|
|
42
|
-
const memoizedValidator = useMemo(()=>{
|
|
43
|
-
return editorSchemaValidator(editorSchema);
|
|
44
|
-
}, [
|
|
45
|
-
editorSchema
|
|
46
|
-
]);
|
|
47
|
-
const memoizedSchema = useMemo(()=>jsonSchema(editorSchema, {
|
|
48
|
-
validate: (value)=>{
|
|
49
|
-
const isValid = memoizedValidator(value);
|
|
50
|
-
if (isValid) {
|
|
51
|
-
return {
|
|
52
|
-
success: true,
|
|
53
|
-
value
|
|
54
|
-
};
|
|
55
|
-
} else {
|
|
56
|
-
return {
|
|
57
|
-
error: new Error('Invalid schema'),
|
|
58
|
-
success: false
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}), [
|
|
63
|
-
memoizedValidator
|
|
64
|
-
]);
|
|
65
|
-
// Active JSON schema for useObject based on field type
|
|
66
|
-
const activeSchema = useMemo(()=>{
|
|
67
|
-
const f = field;
|
|
68
|
-
const fieldType = f?.type;
|
|
69
|
-
if (fieldType === 'richText') {
|
|
70
|
-
return memoizedSchema;
|
|
71
|
-
}
|
|
72
|
-
if (f && f.name && fieldType) {
|
|
73
|
-
const schemaJson = fieldToJsonSchema(f);
|
|
74
|
-
if (schemaJson && Object.keys(schemaJson).length > 0) {
|
|
75
|
-
return jsonSchema(schemaJson);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return undefined;
|
|
79
|
-
}, [
|
|
80
|
-
field,
|
|
81
|
-
memoizedSchema
|
|
82
|
-
]);
|
|
83
30
|
const { isLoading: loadingObject, object, stop: objectStop, submit } = useObject({
|
|
84
31
|
api: `/api${PLUGIN_API_ENDPOINT_GENERATE}`,
|
|
85
32
|
onError: (error)=>{
|
|
@@ -90,8 +37,8 @@ export const useGenerate = ({ instructionId })=>{
|
|
|
90
37
|
if (result.object && field) {
|
|
91
38
|
if (field.type === 'richText') {
|
|
92
39
|
setHistory(result.object);
|
|
93
|
-
|
|
94
|
-
} else if ('name' in field) {
|
|
40
|
+
setSafeLexicalState(result.object, editor);
|
|
41
|
+
} else if ('name' in field && result.object[field.name]) {
|
|
95
42
|
setHistory(result.object[field.name]);
|
|
96
43
|
setValue(result.object[field.name]);
|
|
97
44
|
}
|
|
@@ -99,25 +46,23 @@ export const useGenerate = ({ instructionId })=>{
|
|
|
99
46
|
console.log('onFinish: result, field ', result, field);
|
|
100
47
|
}
|
|
101
48
|
},
|
|
102
|
-
schema:
|
|
49
|
+
schema: jsonSchema({
|
|
50
|
+
type: 'object',
|
|
51
|
+
additionalProperties: true,
|
|
52
|
+
properties: {}
|
|
53
|
+
})
|
|
103
54
|
});
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
requestAnimationFrame(()=>{
|
|
109
|
-
if (field?.type === 'richText') {
|
|
110
|
-
setSafeLexicalState(object, editor);
|
|
111
|
-
} else if (field && 'name' in field && object[field.name]) {
|
|
112
|
-
setValue(object[field.name]);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
}, [
|
|
116
|
-
object,
|
|
55
|
+
const { editor } = editorConfigContext;
|
|
56
|
+
// Hook: Handle high-frequency streaming updates
|
|
57
|
+
useStreamingUpdate({
|
|
117
58
|
editor,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
59
|
+
isLoading: loadingObject,
|
|
60
|
+
object
|
|
61
|
+
});
|
|
62
|
+
// Hook 2: Handle Upload generation and polling
|
|
63
|
+
const { generateUpload, isJobActive, jobProgress, jobStatus } = useGenerateUpload({
|
|
64
|
+
instructionIdRef
|
|
65
|
+
});
|
|
121
66
|
const streamObject = useCallback(({ action = 'Compose', params })=>{
|
|
122
67
|
const doc = getData();
|
|
123
68
|
const currentInstructionId = instructionIdRef.current;
|
|
@@ -138,103 +83,10 @@ export const useGenerate = ({ instructionId })=>{
|
|
|
138
83
|
}, [
|
|
139
84
|
localFromContext?.code,
|
|
140
85
|
instructionIdRef,
|
|
141
|
-
documentId
|
|
142
|
-
]);
|
|
143
|
-
const generateUpload = useCallback(async ()=>{
|
|
144
|
-
const doc = getData();
|
|
145
|
-
const currentInstructionId = instructionIdRef.current;
|
|
146
|
-
return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {
|
|
147
|
-
body: JSON.stringify({
|
|
148
|
-
collectionSlug: collectionSlug ?? '',
|
|
149
|
-
doc,
|
|
150
|
-
documentId,
|
|
151
|
-
locale: localFromContext?.code,
|
|
152
|
-
options: {
|
|
153
|
-
instructionId: currentInstructionId
|
|
154
|
-
}
|
|
155
|
-
}),
|
|
156
|
-
credentials: 'include',
|
|
157
|
-
headers: {
|
|
158
|
-
'Content-Type': 'application/json'
|
|
159
|
-
},
|
|
160
|
-
method: 'POST'
|
|
161
|
-
}).then(async (uploadResponse)=>{
|
|
162
|
-
if (uploadResponse.ok) {
|
|
163
|
-
const json = await uploadResponse.json();
|
|
164
|
-
const { job, result } = json || {};
|
|
165
|
-
if (result) {
|
|
166
|
-
// Set the upload ID
|
|
167
|
-
setValue(result?.id);
|
|
168
|
-
setHistory(result?.id);
|
|
169
|
-
// Show toast to prompt user to save
|
|
170
|
-
toast.success('Image generated successfully! Click Save to see the preview.');
|
|
171
|
-
return uploadResponse;
|
|
172
|
-
}
|
|
173
|
-
// Async job: poll AI Jobs collection for status/progress/result_id
|
|
174
|
-
if (job && job.id) {
|
|
175
|
-
setIsJobActive(true);
|
|
176
|
-
const cancelled = false;
|
|
177
|
-
let attempts = 0;
|
|
178
|
-
const maxAttempts = 600 // up to ~10 minutes @ 1s
|
|
179
|
-
;
|
|
180
|
-
// Basic in-hook state via closure variables; UI will re-render off fetches below
|
|
181
|
-
const poll = async ()=>{
|
|
182
|
-
if (cancelled) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
try {
|
|
186
|
-
const res = await fetch(`${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`, {
|
|
187
|
-
credentials: 'include'
|
|
188
|
-
});
|
|
189
|
-
if (res.ok) {
|
|
190
|
-
const jobDoc = await res.json();
|
|
191
|
-
const { progress, result_id, status } = jobDoc || {};
|
|
192
|
-
setJobStatus(status);
|
|
193
|
-
setJobProgress(progress ?? 0);
|
|
194
|
-
// When result present, set field and finish
|
|
195
|
-
if (status === 'completed' && result_id) {
|
|
196
|
-
// Force upload field to refetch by clearing then setting the ID
|
|
197
|
-
setValue(null);
|
|
198
|
-
setTimeout(()=>{
|
|
199
|
-
setValue(result_id);
|
|
200
|
-
}, 0);
|
|
201
|
-
setHistory(result_id);
|
|
202
|
-
setIsJobActive(false);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (status === 'failed') {
|
|
206
|
-
setIsJobActive(false);
|
|
207
|
-
throw new Error('Video generation failed');
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
} catch (e) {
|
|
211
|
-
// silent retry
|
|
212
|
-
}
|
|
213
|
-
attempts += 1;
|
|
214
|
-
if (!cancelled && attempts < maxAttempts) {
|
|
215
|
-
setTimeout(poll, 1000);
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
setTimeout(poll, 1000);
|
|
219
|
-
return uploadResponse;
|
|
220
|
-
}
|
|
221
|
-
throw new Error('generateUpload: Unexpected response');
|
|
222
|
-
} else {
|
|
223
|
-
const { errors = [] } = await uploadResponse.json();
|
|
224
|
-
const errStr = errors.map((error)=>error.message).join(', ');
|
|
225
|
-
throw new Error(errStr);
|
|
226
|
-
}
|
|
227
|
-
}).catch((error)=>{
|
|
228
|
-
toast.error(`Failed to generate: ${error.message}`);
|
|
229
|
-
console.error('Error generating or setting your upload, please set it manually if its saved in your media files.', error);
|
|
230
|
-
});
|
|
231
|
-
}, [
|
|
232
|
-
getData,
|
|
233
|
-
localFromContext?.code,
|
|
234
|
-
instructionIdRef,
|
|
235
|
-
setValue,
|
|
236
86
|
documentId,
|
|
237
|
-
|
|
87
|
+
getData,
|
|
88
|
+
submit,
|
|
89
|
+
editor
|
|
238
90
|
]);
|
|
239
91
|
const generate = useCallback(async (options)=>{
|
|
240
92
|
if (field?.type === 'upload') {
|