@ai-stack/payloadcms 3.68.0 → 3.76.0-beta.0
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/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 +3 -3
- package/dist/ai/core/streamObject.js.map +1 -1
- package/dist/ai/core/types.d.ts +3 -0
- package/dist/ai/core/types.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/anthropic.js +2 -1
- package/dist/ai/providers/blocks/anthropic.js.map +1 -1
- package/dist/ai/providers/blocks/elevenlabs.js +3 -2
- 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 +11 -6
- 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 +3 -2
- 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 -24
- 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/AIJobs.js +1 -1
- package/dist/collections/AIJobs.js.map +1 -1
- package/dist/collections/AISettings.js +47 -20
- package/dist/collections/AISettings.js.map +1 -1
- package/dist/collections/Instructions.js +37 -0
- package/dist/collections/Instructions.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/chat.d.ts +4 -0
- package/dist/endpoints/fetchFields.js +10 -0
- package/dist/endpoints/fetchFields.js.map +1 -1
- package/dist/endpoints/fetchVoices.js +41 -24
- package/dist/endpoints/fetchVoices.js.map +1 -1
- package/dist/endpoints/index.js +194 -16
- 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 +3 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/payload-ai.d.ts +152 -0
- package/dist/plugin.js +16 -32
- package/dist/plugin.js.map +1 -1
- package/dist/providers/InstructionsProvider/InstructionsProvider.js +47 -15
- package/dist/providers/InstructionsProvider/InstructionsProvider.js.map +1 -1
- package/dist/providers/InstructionsProvider/InstructionsProvider.jsx +39 -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 +21 -2
- 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/types.d.ts +34 -5
- package/dist/types.js +1 -0
- 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.d.ts +1 -0
- package/dist/ui/Compose/Compose.js +23 -4
- package/dist/ui/Compose/Compose.js.map +1 -1
- package/dist/ui/Compose/Compose.jsx +23 -4
- 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/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/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/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/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/EncryptedTextField/index.js +4 -4
- package/dist/ui/EncryptedTextField/index.js.map +1 -1
- package/dist/ui/EncryptedTextField/index.jsx +4 -4
- package/dist/ui/VoicesFetcher/index.js +34 -16
- package/dist/ui/VoicesFetcher/index.js.map +1 -1
- package/dist/ui/VoicesFetcher/index.jsx +32 -15
- package/dist/utilities/buildSmartPrompt.d.ts +22 -0
- package/dist/utilities/buildSmartPrompt.js +141 -0
- package/dist/utilities/buildSmartPrompt.js.map +1 -0
- package/dist/utilities/encryption.js +2 -1
- package/dist/utilities/encryption.js.map +1 -1
- package/dist/utilities/fieldToJsonSchema.js +32 -3
- package/dist/utilities/fieldToJsonSchema.js.map +1 -1
- 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/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 +8 -1
- package/dist/utilities/updateFieldsConfig.js.map +1 -1
- package/package.json +35 -33
- package/dist/endpoints/chat.d.js +0 -3
- package/dist/endpoints/chat.d.js.map +0 -1
- package/dist/init.d.ts +0 -7
- package/dist/init.js +0 -135
- package/dist/init.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,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
3
|
+
import { useForm } from '@payloadcms/ui';
|
|
4
|
+
import { getSiblingData } from 'payload/shared';
|
|
4
5
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
5
6
|
import { useFieldProps } from '../../../../providers/FieldProvider/useFieldProps.js';
|
|
6
7
|
import { Compose, Proofread, Rephrase } from './items.js';
|
|
@@ -19,14 +20,27 @@ const getActiveComponent = (ac)=>{
|
|
|
19
20
|
}
|
|
20
21
|
};
|
|
21
22
|
export const useMenu = (menuEvents, options)=>{
|
|
22
|
-
const { field: { type: fieldType } = {}, path
|
|
23
|
-
const
|
|
24
|
-
path: pathFromContext ?? ''
|
|
25
|
-
});
|
|
23
|
+
const { field: { type: fieldType } = {}, path } = useFieldProps();
|
|
24
|
+
const { getData } = useForm();
|
|
26
25
|
const [activeComponent, setActiveComponent] = useState('Rephrase');
|
|
27
|
-
|
|
26
|
+
// Check value once on mount or when path/type changes
|
|
28
27
|
useEffect(()=>{
|
|
29
|
-
|
|
28
|
+
let hasValue = false;
|
|
29
|
+
try {
|
|
30
|
+
const data = getData();
|
|
31
|
+
if (path) {
|
|
32
|
+
const val = getSiblingData(data, path);
|
|
33
|
+
hasValue = val !== undefined && val !== null;
|
|
34
|
+
// For richTextFields, we might need a more robust check (e.g. check for root.children.length > 0)
|
|
35
|
+
// But for now, simple truthiness covers most cases or at least defaults safely
|
|
36
|
+
if (fieldType === 'richText' && val && typeof val === 'object' && 'root' in val) {
|
|
37
|
+
// Basic lexical check could go here if needed
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} catch (e) {
|
|
41
|
+
// ignore
|
|
42
|
+
}
|
|
43
|
+
if (!hasValue) {
|
|
30
44
|
setActiveComponent('Compose');
|
|
31
45
|
return;
|
|
32
46
|
}
|
|
@@ -34,15 +48,12 @@ export const useMenu = (menuEvents, options)=>{
|
|
|
34
48
|
setActiveComponent('Compose');
|
|
35
49
|
return;
|
|
36
50
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} else {
|
|
40
|
-
setActiveComponent('Rephrase');
|
|
41
|
-
}
|
|
51
|
+
// Default to Rephrase if value exists
|
|
52
|
+
setActiveComponent('Rephrase');
|
|
42
53
|
}, [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
fieldType,
|
|
55
|
+
getData,
|
|
56
|
+
path
|
|
46
57
|
]);
|
|
47
58
|
const MemoizedActiveComponent = useMemo(()=>{
|
|
48
59
|
return ({ isLoading, loadingLabel, stop })=>{
|
|
@@ -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,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') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/Compose/hooks/useGenerate.ts"],"sourcesContent":["import { experimental_useObject as useObject } from '@ai-sdk/react'\nimport { useEditorConfigContext } from '@payloadcms/richtext-lexical/client'\nimport { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui'\nimport { jsonSchema } from 'ai'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport type { ActionMenuItems, GenerateTextarea } from '../../../types.js'\n\nimport {\n PLUGIN_AI_JOBS_TABLE,\n PLUGIN_API_ENDPOINT_GENERATE,\n PLUGIN_API_ENDPOINT_GENERATE_UPLOAD,\n PLUGIN_INSTRUCTIONS_TABLE,\n PLUGIN_NAME,\n} from '../../../defaults.js'\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\nimport { editorSchemaValidator } from '../../../utilities/editorSchemaValidator.js'\nimport { fieldToJsonSchema } from '../../../utilities/fieldToJsonSchema.js'\nimport { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js'\nimport { useHistory } from './useHistory.js'\n\ntype ActionCallbackParams = { action: ActionMenuItems; params?: unknown }\n\nexport const useGenerate = ({ instructionId }: { instructionId: string }) => {\n // Create a ref to hold the current instructionId\n const instructionIdRef = useRef(instructionId)\n\n // Update the ref whenever instructionId changes\n useEffect(() => {\n instructionIdRef.current = instructionId\n }, [instructionId])\n\n const { field, path: pathFromContext } = useFieldProps()\n const editorConfigContext = useEditorConfigContext()\n\n const { editor } = editorConfigContext\n\n const { config } = useConfig()\n const {\n routes: { api },\n serverURL,\n } = config\n\n const { setValue } = useField<any>({\n path: pathFromContext ?? '',\n })\n\n const { set: setHistory } = useHistory()\n\n // Async job UI state\n const [jobStatus, setJobStatus] = useState<string | undefined>(undefined)\n const [jobProgress, setJobProgress] = useState<number>(0)\n const [isJobActive, setIsJobActive] = useState<boolean>(false)\n\n const { getData } = useForm()\n const { id: documentId, collectionSlug } = useDocumentInfo()\n\n const localFromContext = useLocale()\n \n // Reuse config from above instead of calling useConfig again\n const { collections } = config\n\n const collection = collections.find((collection) => collection.slug === PLUGIN_INSTRUCTIONS_TABLE)\n const { custom: { [PLUGIN_NAME]: { editorConfig = {} } = {} } = {} } = collection?.admin ?? {}\n const { schema: editorSchema = {} } = editorConfig\n\n const memoizedValidator = useMemo(() => {\n return editorSchemaValidator(editorSchema)\n }, [editorSchema])\n\n const memoizedSchema = useMemo(\n () =>\n jsonSchema(editorSchema, {\n validate: (value) => {\n const isValid = memoizedValidator(value)\n\n if (isValid) {\n return {\n success: true,\n value,\n }\n } else {\n return {\n error: new Error('Invalid schema'),\n success: false,\n }\n }\n },\n }),\n [memoizedValidator],\n )\n\n // Active JSON schema for useObject based on field type\n const activeSchema = useMemo(() => {\n const f = field as any\n const fieldType = f?.type as string | undefined\n if (fieldType === 'richText') {\n return memoizedSchema\n }\n if (f && f.name && fieldType) {\n const schemaJson = fieldToJsonSchema(f)\n if (schemaJson && Object.keys(schemaJson).length > 0) {\n return jsonSchema(schemaJson)\n }\n }\n return undefined\n }, [field, memoizedSchema])\n\n const {\n isLoading: loadingObject,\n object,\n stop: objectStop,\n submit,\n } = useObject({\n api: `/api${PLUGIN_API_ENDPOINT_GENERATE}`,\n onError: (error: any) => {\n toast.error(`Failed to generate: ${error.message}`)\n console.error('Error generating object:', error)\n },\n onFinish: (result) => {\n if (result.object && field) {\n if (field.type === 'richText') {\n setHistory(result.object)\n setValue(result.object)\n } else if ('name' in field) {\n setHistory(result.object[field.name])\n setValue(result.object[field.name])\n }\n } else {\n console.log('onFinish: result, field ', result, field)\n }\n },\n schema: activeSchema as any,\n })\n\n useEffect(() => {\n if (!object) {\n return\n }\n\n requestAnimationFrame(() => {\n if (field?.type === 'richText') {\n setSafeLexicalState(object, editor)\n } else if (field && 'name' in field && object[field.name]) {\n setValue(object[field.name])\n }\n })\n }, [object, editor, field, setValue])\n\n const streamObject = useCallback(\n ({ action = 'Compose', params }: ActionCallbackParams) => {\n const doc = getData()\n\n const currentInstructionId = instructionIdRef.current\n\n const options = {\n action,\n actionParams: params,\n instructionId: currentInstructionId,\n }\n\n submit({\n allowedEditorNodes: Array.from(editor?._nodes?.keys() || []),\n doc: {\n ...doc,\n id: documentId,\n },\n locale: localFromContext?.code,\n options,\n })\n },\n [localFromContext?.code, instructionIdRef, documentId],\n )\n\n const generateUpload = useCallback(async () => {\n const doc = getData()\n const currentInstructionId = instructionIdRef.current\n\n return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {\n body: JSON.stringify({\n collectionSlug: collectionSlug ?? '',\n doc,\n documentId,\n locale: localFromContext?.code,\n options: {\n instructionId: currentInstructionId,\n },\n } satisfies Parameters<GenerateTextarea>[0]),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n .then(async (uploadResponse) => {\n if (uploadResponse.ok) {\n const json = await uploadResponse.json()\n const { job, result } = json || {}\n if (result) {\n // Set the upload ID\n setValue(result?.id)\n setHistory(result?.id)\n \n // Show toast to prompt user to save\n toast.success('Image generated successfully! Click Save to see the preview.')\n \n return uploadResponse\n }\n\n // Async job: poll AI Jobs collection for status/progress/result_id\n if (job && job.id) {\n setIsJobActive(true)\n const cancelled = false\n let attempts = 0\n const maxAttempts = 600 // up to ~10 minutes @ 1s\n\n // Basic in-hook state via closure variables; UI will re-render off fetches below\n const poll = async (): Promise<void> => {\n if (cancelled) {\n return\n }\n try {\n const res = await fetch(\n `${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`,\n { credentials: 'include' },\n )\n if (res.ok) {\n const jobDoc = await res.json()\n const { progress, result_id, status } = jobDoc || {}\n setJobStatus(status)\n setJobProgress(progress ?? 0)\n // When result present, set field and finish\n if (status === 'completed' && result_id) {\n // Force upload field to refetch by clearing then setting the ID\n setValue(null)\n setTimeout(() => {\n setValue(result_id)\n }, 0)\n setHistory(result_id)\n setIsJobActive(false)\n return\n }\n if (status === 'failed') {\n setIsJobActive(false)\n throw new Error('Video generation failed')\n }\n }\n } catch (e) {\n // silent retry\n }\n\n attempts += 1\n if (!cancelled && attempts < maxAttempts) {\n setTimeout(poll, 1000)\n }\n }\n setTimeout(poll, 1000)\n return uploadResponse\n }\n\n throw new Error('generateUpload: Unexpected response')\n } else {\n const { errors = [] } = await uploadResponse.json()\n const errStr = errors.map((error: any) => error.message).join(', ')\n throw new Error(errStr)\n }\n })\n .catch((error) => {\n toast.error(`Failed to generate: ${error.message}`)\n console.error(\n 'Error generating or setting your upload, please set it manually if its saved in your media files.',\n error,\n )\n })\n }, [getData, localFromContext?.code, instructionIdRef, setValue, documentId, collectionSlug])\n\n const generate = useCallback(\n async (options?: ActionCallbackParams) => {\n if ((field as any)?.type === 'upload') {\n return generateUpload()\n }\n // All supported types use structured object generation when schema is provided server-side\n return streamObject(options ?? { action: 'Compose' })\n },\n [generateUpload, streamObject, field],\n )\n\n const stop = useCallback(() => {\n console.log('Stopping...')\n objectStop()\n }, [objectStop])\n\n return {\n generate,\n isJobActive,\n isLoading: loadingObject,\n jobProgress,\n jobStatus,\n stop,\n }\n}\n"],"names":["experimental_useObject","useObject","useEditorConfigContext","toast","useConfig","useDocumentInfo","useField","useForm","useLocale","jsonSchema","useCallback","useEffect","useMemo","useRef","useState","PLUGIN_AI_JOBS_TABLE","PLUGIN_API_ENDPOINT_GENERATE","PLUGIN_API_ENDPOINT_GENERATE_UPLOAD","PLUGIN_INSTRUCTIONS_TABLE","PLUGIN_NAME","useFieldProps","editorSchemaValidator","fieldToJsonSchema","setSafeLexicalState","useHistory","useGenerate","instructionId","instructionIdRef","current","field","path","pathFromContext","editorConfigContext","editor","config","routes","api","serverURL","setValue","set","setHistory","jobStatus","setJobStatus","undefined","jobProgress","setJobProgress","isJobActive","setIsJobActive","getData","id","documentId","collectionSlug","localFromContext","collections","collection","find","slug","custom","editorConfig","admin","schema","editorSchema","memoizedValidator","memoizedSchema","validate","value","isValid","success","error","Error","activeSchema","f","fieldType","type","name","schemaJson","Object","keys","length","isLoading","loadingObject","object","stop","objectStop","submit","onError","message","console","onFinish","result","log","requestAnimationFrame","streamObject","action","params","doc","currentInstructionId","options","actionParams","allowedEditorNodes","Array","from","_nodes","locale","code","generateUpload","fetch","body","JSON","stringify","credentials","headers","method","then","uploadResponse","ok","json","job","cancelled","attempts","maxAttempts","poll","res","jobDoc","progress","result_id","status","setTimeout","e","errors","errStr","map","join","catch","generate"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/Compose/hooks/useGenerate.ts"],"sourcesContent":["import { experimental_useObject as useObject } from '@ai-sdk/react'\nimport { useEditorConfigContext } from '@payloadcms/richtext-lexical/client'\nimport { toast, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui'\nimport { jsonSchema } from 'ai'\nimport { useCallback, useEffect, useRef } from 'react'\n\nimport type { ActionMenuItems } from '../../../types.js'\n\nimport { PLUGIN_API_ENDPOINT_GENERATE } from '../../../defaults.js'\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\nimport { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js'\nimport { useGenerateUpload } from './useGenerateUpload.js'\nimport { useHistory } from './useHistory.js'\nimport { useStreamingUpdate } from './useStreamingUpdate.js'\n\ntype ActionCallbackParams = { action: ActionMenuItems; params?: unknown }\n\nexport const useGenerate = ({ instructionId }: { instructionId: string }) => {\n // Create a ref to hold the current instructionId\n const instructionIdRef = useRef(instructionId)\n\n // Update the ref whenever instructionId changes\n useEffect(() => {\n instructionIdRef.current = instructionId\n }, [instructionId])\n\n const { field, path: pathFromContext } = useFieldProps()\n const editorConfigContext = useEditorConfigContext()\n const { setValue } = useField<any>({\n path: pathFromContext ?? '',\n })\n const { set: setHistory } = useHistory()\n const { getData } = useForm()\n const { id: documentId } = useDocumentInfo()\n const localFromContext = useLocale()\n\n const {\n isLoading: loadingObject,\n object,\n stop: objectStop,\n submit,\n } = useObject({\n api: `/api${PLUGIN_API_ENDPOINT_GENERATE}`,\n onError: (error: any) => {\n toast.error(`Failed to generate: ${error.message}`)\n console.error('Error generating object:', error)\n },\n onFinish: (result) => {\n if (result.object && field) {\n if (field.type === 'richText') {\n setHistory(result.object)\n setSafeLexicalState(result.object, editor)\n } else if ('name' in field && result.object[field.name]) {\n setHistory(result.object[field.name])\n setValue(result.object[field.name])\n }\n } else {\n console.log('onFinish: result, field ', result, field)\n }\n },\n schema: jsonSchema({\n type: 'object',\n additionalProperties: true,\n properties: {},\n }) as any,\n })\n\n const { editor } = editorConfigContext\n\n // Hook: Handle high-frequency streaming updates\n useStreamingUpdate({\n editor,\n isLoading: loadingObject,\n object,\n })\n\n // Hook 2: Handle Upload generation and polling\n const { generateUpload, isJobActive, jobProgress, jobStatus } = useGenerateUpload({\n instructionIdRef,\n })\n\n const streamObject = useCallback(\n ({ action = 'Compose', params }: ActionCallbackParams) => {\n const doc = getData()\n\n const currentInstructionId = instructionIdRef.current\n\n const options = {\n action,\n actionParams: params,\n instructionId: currentInstructionId,\n }\n\n submit({\n allowedEditorNodes: Array.from(editor?._nodes?.keys() || []),\n doc: {\n ...doc,\n id: documentId,\n },\n locale: localFromContext?.code,\n options,\n })\n },\n [localFromContext?.code, instructionIdRef, documentId, getData, submit, editor],\n )\n\n const generate = useCallback(\n async (options?: ActionCallbackParams) => {\n if ((field as any)?.type === 'upload') {\n return generateUpload()\n }\n // All supported types use structured object generation when schema is provided server-side\n return streamObject(options ?? { action: 'Compose' })\n },\n [generateUpload, streamObject, field],\n )\n\n const stop = useCallback(() => {\n console.log('Stopping...')\n objectStop()\n }, [objectStop])\n\n return {\n generate,\n isJobActive,\n isLoading: loadingObject,\n jobProgress,\n jobStatus,\n stop,\n }\n}\n"],"names":["experimental_useObject","useObject","useEditorConfigContext","toast","useDocumentInfo","useField","useForm","useLocale","jsonSchema","useCallback","useEffect","useRef","PLUGIN_API_ENDPOINT_GENERATE","useFieldProps","setSafeLexicalState","useGenerateUpload","useHistory","useStreamingUpdate","useGenerate","instructionId","instructionIdRef","current","field","path","pathFromContext","editorConfigContext","setValue","set","setHistory","getData","id","documentId","localFromContext","isLoading","loadingObject","object","stop","objectStop","submit","api","onError","error","message","console","onFinish","result","type","editor","name","log","schema","additionalProperties","properties","generateUpload","isJobActive","jobProgress","jobStatus","streamObject","action","params","doc","currentInstructionId","options","actionParams","allowedEditorNodes","Array","from","_nodes","keys","locale","code","generate"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
type UseGenerateUploadParams = {
|
|
3
|
+
instructionIdRef: RefObject<string>;
|
|
4
|
+
};
|
|
5
|
+
export declare const useGenerateUpload: ({ instructionIdRef }: UseGenerateUploadParams) => {
|
|
6
|
+
generateUpload: () => Promise<void | Response>;
|
|
7
|
+
isJobActive: boolean;
|
|
8
|
+
jobProgress: number;
|
|
9
|
+
jobStatus: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui';
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
import { PLUGIN_AI_JOBS_TABLE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD } from '../../../defaults.js';
|
|
4
|
+
import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js';
|
|
5
|
+
import { useHistory } from './useHistory.js';
|
|
6
|
+
export const useGenerateUpload = ({ instructionIdRef })=>{
|
|
7
|
+
const { config } = useConfig();
|
|
8
|
+
const { routes: { api }, serverURL } = config;
|
|
9
|
+
const { id: documentId, collectionSlug } = useDocumentInfo();
|
|
10
|
+
const localFromContext = useLocale();
|
|
11
|
+
const { getData } = useForm();
|
|
12
|
+
const { set: setHistory } = useHistory();
|
|
13
|
+
const { field, path: pathFromContext } = useFieldProps();
|
|
14
|
+
const { setValue } = useField({
|
|
15
|
+
path: pathFromContext ?? ''
|
|
16
|
+
});
|
|
17
|
+
// Async job UI state
|
|
18
|
+
const [jobStatus, setJobStatus] = useState(undefined);
|
|
19
|
+
const [jobProgress, setJobProgress] = useState(0);
|
|
20
|
+
const [isJobActive, setIsJobActive] = useState(false);
|
|
21
|
+
const generateUpload = useCallback(async ()=>{
|
|
22
|
+
const doc = getData();
|
|
23
|
+
const currentInstructionId = instructionIdRef.current;
|
|
24
|
+
return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
collectionSlug: collectionSlug ?? '',
|
|
27
|
+
doc,
|
|
28
|
+
documentId,
|
|
29
|
+
locale: localFromContext?.code,
|
|
30
|
+
options: {
|
|
31
|
+
instructionId: currentInstructionId
|
|
32
|
+
}
|
|
33
|
+
}),
|
|
34
|
+
credentials: 'include',
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json'
|
|
37
|
+
},
|
|
38
|
+
method: 'POST'
|
|
39
|
+
}).then(async (uploadResponse)=>{
|
|
40
|
+
if (uploadResponse.ok) {
|
|
41
|
+
const json = await uploadResponse.json();
|
|
42
|
+
const { job, result } = json || {};
|
|
43
|
+
if (result) {
|
|
44
|
+
setValue(result?.id);
|
|
45
|
+
setHistory(result?.id);
|
|
46
|
+
// Show toast to prompt user to save
|
|
47
|
+
toast.success('Image generated successfully! Click Save to see the preview.');
|
|
48
|
+
return uploadResponse;
|
|
49
|
+
}
|
|
50
|
+
// Async job: poll AI Jobs collection for status/progress/result_id
|
|
51
|
+
if (job && job.id) {
|
|
52
|
+
setIsJobActive(true);
|
|
53
|
+
const cancelled = false;
|
|
54
|
+
let attempts = 0;
|
|
55
|
+
const maxAttempts = 600 // up to ~10 minutes @ 1s
|
|
56
|
+
;
|
|
57
|
+
// Basic in-hook state via closure variables; UI will re-render off fetches below
|
|
58
|
+
const poll = async ()=>{
|
|
59
|
+
if (cancelled) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const res = await fetch(`${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`, {
|
|
64
|
+
credentials: 'include'
|
|
65
|
+
});
|
|
66
|
+
if (res.ok) {
|
|
67
|
+
const jobDoc = await res.json();
|
|
68
|
+
const { progress, result_id, status } = jobDoc || {};
|
|
69
|
+
setJobStatus(status);
|
|
70
|
+
setJobProgress(progress ?? 0);
|
|
71
|
+
// When result present, set field and finish
|
|
72
|
+
if (status === 'completed' && result_id) {
|
|
73
|
+
let valueToSet = result_id;
|
|
74
|
+
// Attempt to fetch full document for immediate preview
|
|
75
|
+
if (field && 'relationTo' in field && typeof field.relationTo === 'string') {
|
|
76
|
+
let attempts = 0;
|
|
77
|
+
const maxAttempts = 3;
|
|
78
|
+
while(attempts < maxAttempts){
|
|
79
|
+
try {
|
|
80
|
+
const docRes = await fetch(`${serverURL}${api}/${field.relationTo}/${result_id}`, {
|
|
81
|
+
credentials: 'include'
|
|
82
|
+
});
|
|
83
|
+
if (docRes.ok) {
|
|
84
|
+
const doc = await docRes.json();
|
|
85
|
+
// Verify we have a URL for preview
|
|
86
|
+
if (doc && doc.url) {
|
|
87
|
+
valueToSet = doc;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.error('Failed to fetch generated document for preview:', e);
|
|
93
|
+
}
|
|
94
|
+
attempts++;
|
|
95
|
+
if (attempts < maxAttempts) {
|
|
96
|
+
await new Promise((resolve)=>setTimeout(resolve, 500));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
setValue(valueToSet);
|
|
101
|
+
setHistory(result_id);
|
|
102
|
+
setIsJobActive(false);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (status === 'failed') {
|
|
106
|
+
setIsJobActive(false);
|
|
107
|
+
throw new Error('Video generation failed');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (_) {
|
|
111
|
+
// silent retry
|
|
112
|
+
}
|
|
113
|
+
attempts += 1;
|
|
114
|
+
if (!cancelled && attempts < maxAttempts) {
|
|
115
|
+
setTimeout(poll, 1000);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
setTimeout(poll, 1000);
|
|
119
|
+
return uploadResponse;
|
|
120
|
+
}
|
|
121
|
+
throw new Error('generateUpload: Unexpected response');
|
|
122
|
+
} else {
|
|
123
|
+
const { errors = [] } = await uploadResponse.json();
|
|
124
|
+
const errStr = errors.map((error)=>error.message).join(', ');
|
|
125
|
+
throw new Error(errStr);
|
|
126
|
+
}
|
|
127
|
+
}).catch((error)=>{
|
|
128
|
+
toast.error(`Failed to generate: ${error.message}`);
|
|
129
|
+
console.error('Error generating or setting your upload, please set it manually if its saved in your media files.', error);
|
|
130
|
+
});
|
|
131
|
+
}, [
|
|
132
|
+
getData,
|
|
133
|
+
localFromContext?.code,
|
|
134
|
+
instructionIdRef,
|
|
135
|
+
// setValue,
|
|
136
|
+
documentId,
|
|
137
|
+
collectionSlug,
|
|
138
|
+
serverURL,
|
|
139
|
+
api,
|
|
140
|
+
setHistory
|
|
141
|
+
]);
|
|
142
|
+
return {
|
|
143
|
+
generateUpload,
|
|
144
|
+
isJobActive,
|
|
145
|
+
jobProgress,
|
|
146
|
+
jobStatus
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
//# sourceMappingURL=useGenerateUpload.js.map
|