@ai-stack/payloadcms 3.68.0-beta.2 → 3.68.0-beta.3
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/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/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/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 +0 -3
- 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 +5 -3
- package/dist/ui/Compose/Compose.js.map +1 -1
- package/dist/ui/Compose/Compose.jsx +3 -3
- package/dist/ui/Compose/hooks/useGenerate.js +34 -131
- 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 +123 -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/seedProperties.d.ts +7 -0
- package/dist/utilities/seedProperties.js +117 -0
- package/dist/utilities/seedProperties.js.map +1 -0
- package/dist/utilities/setSafeLexicalState.js +80 -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/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(\n (val: any) => {\n if (val && typeof val === 'object' && 'root' in val && lexicalEditor) {\n // Pass the object directly to our safe setter which handles validation\n setSafeLexicalState(val, lexicalEditor)\n }\n },\n [lexicalEditor],\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","popupRender","close","onClose","isAnyLoading","memoizedPopup","button","render","verticalAlign","label","className","actions","actionsVisible","onClick","e","preventDefault","role","loadingLabel","Math","max","min","round","onChange"],"mappings":"AAAA;;AAKA,SAASA,sBAAsB,QAAQ,sCAAqC;AAC5E,SAASC,KAAK,EAAEC,QAAQ,QAAQ,iBAAgB;AAChD,OAAOC,SAASC,WAAW,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAG7D,SAASC,eAAe,QAAQ,0DAAyD;AACzF,SAASC,mBAAmB,QAAQ,yCAAwC;AAC5E,SAASC,UAAU,QAAQ,oBAAmB;AAC9C,OAAOC,YAAY,uBAAsB;AACzC,SAASC,OAAO,QAAQ,0BAAyB;AACjD,SAASC,sBAAsB,QAAQ,oCAAmC;AAC1E,SAASC,WAAW,QAAQ,yBAAwB;AACpD,SAASC,eAAe,QAAQ,uBAAsB;AAatD,OAAO,MAAMC,UAA4B,CAAC,EAAEC,gBAAgB,EAAEC,YAAY,EAAEC,aAAa,EAAEC,eAAe,EAAE;IAC1G,MAAMC,kBAAkBJ,kBAAkBK;IAC1C,MAAM,EAAEC,QAAQC,aAAa,EAAE,GAAGvB;IAElC,qCAAqC;IACrC,MAAM,EAAEwB,UAAU,EAAE,GAAGjB;IAEvB,0CAA0C;IAC1CK;IAEA,MAAM,CAACa,cAAcC,gBAAgB,GAAGpB,SAAkB;IAC1D,MAAM,EAAEqB,QAAQ,EAAEC,WAAW,EAAEC,SAAS,EAAEC,WAAW,EAAEC,SAAS,EAAEC,IAAI,EAAE,GAAGnB,YAAY;QAAEK;IAAc;IAEvG,oEAAoE;IACpE,MAAMe,YAAY7B,YAAY;QAC5B8B,QAAQC,GAAG,CAAC;QACZT,gBAAgB;QAChBC,SAAS;YACPS,QAAQ;QACV,GACGC,KAAK,CAAC,CAACC;YACNJ,QAAQK,KAAK,CAAC,cAAcD;QAC9B,GACCE,OAAO,CAAC;YACPd,gBAAgB;QAClB;IACJ,GAAG;QAACC;KAAS;IAEb,MAAMc,WAAWrC,YAAY;QAC3B8B,QAAQC,GAAG,CAAC;QACZR,SAAS;YACPS,QAAQ;QACV,GACGC,KAAK,CAAC,CAACC;YACNJ,QAAQK,KAAK,CAAC,cAAcD;QAC9B,GACCE,OAAO,CAAC;YACPd,gBAAgB;QAClB;IACJ,GAAG;QAACC;KAAS;IAEb,MAAMe,cAActC,YAAY;QAC9B8B,QAAQC,GAAG,CAAC;QACZR,SAAS;YACPS,QAAQ;QACV,GACGC,KAAK,CAAC,CAACC;YACNJ,QAAQK,KAAK,CAAC,cAAcD;QAC9B,GACCE,OAAO,CAAC;YACPd,gBAAgB;QAClB;IACJ,GAAG;QAACC;KAAS;IAEb,MAAMgB,aAAavC,YAAY;QAC7B8B,QAAQC,GAAG,CAAC;QACZR,SAAS;YACPS,QAAQ;QACV,GACGC,KAAK,CAAC,CAACC;YACNJ,QAAQK,KAAK,CAAC,cAAcD;QAC9B,GACCE,OAAO,CAAC;YACPd,gBAAgB;QAClB;IACJ,GAAG;QAACC;KAAS;IAEb,MAAMiB,aAAaxC,YAAY;QAC7B8B,QAAQC,GAAG,CAAC;QACZR,SAAS;YACPS,QAAQ;QACV,GACGC,KAAK,CAAC,CAACC;YACNJ,QAAQK,KAAK,CAAC,cAAcD;QAC9B,GACCE,OAAO,CAAC;YACPd,gBAAgB;QAClB;IACJ,GAAG;QAACC;KAAS;IAEb,MAAMkB,cAAczC,YAAY;QAC9B8B,QAAQC,GAAG,CAAC;QACZR,SAAS;YACPS,QAAQ;QACV,GACGC,KAAK,CAAC,CAACC;YACNJ,QAAQK,KAAK,CAAC,cAAcD;QAC9B,GACCE,OAAO,CAAC;YACPd,gBAAgB;QAClB;IACJ,GAAG;QAACC;KAAS;IAEb,MAAMmB,cAAc1C,YAAY,CAAC2C;QAC/Bb,QAAQC,GAAG,CAAC;QACZR,SAAS;YACPS,QAAQ;YACRY,QAAQD;QACV,GACGV,KAAK,CAAC,CAACC;YACNJ,QAAQK,KAAK,CAAC,cAAcD;QAC9B,GACCE,OAAO,CAAC;YACPd,gBAAgB;QAClB;IACJ,GAAG;QAACC;KAAS;IAEb,MAAMsB,qBAAqB7C,YAAY;QACrC,IAAIe,iBAAiB;YACnBK,WAAWN;QACb;IACF,GAAG;QAACC;QAAiBK;QAAYN;KAAc;IAE/C,MAAM,EAAEgC,eAAe,EAAEC,IAAI,EAAE,GAAGxC,QAChC;QACEsB;QACAQ;QACAC;QACAC;QACAS,YAAYjC,kBAAkB8B,qBAAqBI;QACnDT;QACAC;QACAC;IACF,GACA;QACE3B;IACF;IAGF,MAAM,EAAEmC,QAAQ,EAAE,GAAGpD,SAAiB;QACpCmB,MAAMD;IACR;IAEA,MAAMmC,2BAA2BnD,YAC/B,CAACoD;QACC,IAAIA,OAAO,OAAOA,QAAQ,YAAY,UAAUA,OAAOjC,eAAe;YACpE,uEAAuE;YACvEf,oBAAoBgD,KAAKjC;QAC3B;IACF,GACA;QAACA;KAAc;IAGjB,MAAMkC,cAAcrD,YAClB,CAAC,EAAEsD,KAAK,EAAyB;QAC/B,qBAAO,KAACP;YAAKtB,WAAWJ,gBAAgBI;YAAW8B,SAASD;;IAC9D,GACA;QAACjC;QAAcI;QAAWsB;KAAK;IAGjC,8CAA8C;IAC9C,MAAMS,eAAenC,gBAAgBI,aAAaD;IAElD,MAAMiC,gBAAgBxD,QAAQ;QAC5B,qBACE,KAACJ;YACC6D,sBAAQ,KAACrD;gBAAWoB,WAAW+B;;YAC/BG,QAAQN;YACRO,eAAc;;IAGpB,GAAG;QAACP;QAAaG;KAAa;IAE9B,qBACE,MAACK;QACCC,WAAW,CAAC,2BAA2B,EAAExD,OAAOyD,OAAO,CAAC,CAAC,EAAElD,eAAeP,OAAO0D,cAAc,GAAG,GAAG,CAAC;QACtGC,SAAS,CAACC,IAAMA,EAAEC,cAAc;QAChCC,MAAK;;YAEJX;0BACD,KAACX;gBACCrB,WAAWJ,gBAAgBI,aAAaD;gBACxC6C,cAAc7C,cAAeG,cAAc,YAAY,CAAC,MAAM,EAAE2C,KAAKC,GAAG,CAAC,GAAGD,KAAKE,GAAG,CAAC,KAAKF,KAAKG,KAAK,CAAC/C,eAAe,KAAK,CAAC,CAAC,GAAIC,aAAa,WAAasB;gBACzJrB,MAAMA;;0BAER,KAAClB;gBACCgE,UAAU,CAACtB;oBACTF,SAASE;oBACTD,yBAAyBC;gBAC3B;;;;AAIR,EAAC"}
|
|
@@ -128,10 +128,10 @@ export const Compose = ({ descriptionProps, forceVisible, instructionId, isConfi
|
|
|
128
128
|
});
|
|
129
129
|
const setIfValueIsLexicalState = useCallback((val) => {
|
|
130
130
|
if (val && typeof val === 'object' && 'root' in val && lexicalEditor) {
|
|
131
|
-
|
|
131
|
+
// Pass the object directly to our safe setter which handles validation
|
|
132
|
+
setSafeLexicalState(val, lexicalEditor);
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
-
}, []);
|
|
134
|
+
}, [lexicalEditor]);
|
|
135
135
|
const popupRender = useCallback(({ close }) => {
|
|
136
136
|
return <Menu isLoading={isProcessing || isLoading} onClose={close}/>;
|
|
137
137
|
}, [isProcessing, isLoading, Menu]);
|
|
@@ -2,40 +2,35 @@ import { experimental_useObject as useObject } from '@ai-sdk/react';
|
|
|
2
2
|
import { useEditorConfigContext } from '@payloadcms/richtext-lexical/client';
|
|
3
3
|
import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui';
|
|
4
4
|
import { jsonSchema } from 'ai';
|
|
5
|
-
import { useCallback,
|
|
6
|
-
import {
|
|
5
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
6
|
+
import { PLUGIN_API_ENDPOINT_GENERATE, PLUGIN_INSTRUCTIONS_TABLE, PLUGIN_NAME } from '../../../defaults.js';
|
|
7
7
|
import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js';
|
|
8
8
|
import { editorSchemaValidator } from '../../../utilities/editorSchemaValidator.js';
|
|
9
9
|
import { fieldToJsonSchema } from '../../../utilities/fieldToJsonSchema.js';
|
|
10
10
|
import { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js';
|
|
11
|
+
import { useGenerateUpload } from './useGenerateUpload.js';
|
|
11
12
|
import { useHistory } from './useHistory.js';
|
|
13
|
+
import { useStreamingUpdate } from './useStreamingUpdate.js';
|
|
12
14
|
export const useGenerate = ({ instructionId })=>{
|
|
13
|
-
// Create a ref to hold the current instructionId
|
|
14
15
|
const instructionIdRef = useRef(instructionId);
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
instructionIdRef.current = instructionId;
|
|
18
|
-
}, [
|
|
19
|
-
instructionId
|
|
20
|
-
]);
|
|
16
|
+
// Sync ref
|
|
17
|
+
instructionIdRef.current = instructionId;
|
|
21
18
|
const { field, path: pathFromContext } = useFieldProps();
|
|
22
|
-
const
|
|
23
|
-
const { editor } = editorConfigContext;
|
|
19
|
+
const { editor } = useEditorConfigContext();
|
|
24
20
|
const { config } = useConfig();
|
|
25
|
-
const {
|
|
21
|
+
const { collections } = config;
|
|
22
|
+
// Use dispatchFields to update values without subscribing to them, avoiding re-renders during streaming
|
|
23
|
+
// dispatchFields is handled inside useStreamingUpdate now, so we don't need it here
|
|
24
|
+
// const { dispatchFields } = useForm()
|
|
25
|
+
// Keep setValue for non-streaming updates (one-off), but do NOT destructure 'value' to avoid subscriptions
|
|
26
26
|
const { setValue } = useField({
|
|
27
27
|
path: pathFromContext ?? ''
|
|
28
28
|
});
|
|
29
29
|
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
30
|
const { getData } = useForm();
|
|
35
|
-
const { id: documentId
|
|
31
|
+
const { id: documentId } = useDocumentInfo();
|
|
36
32
|
const localFromContext = useLocale();
|
|
37
|
-
//
|
|
38
|
-
const { collections } = config;
|
|
33
|
+
// Editor Schema Setup
|
|
39
34
|
const collection = collections.find((collection)=>collection.slug === PLUGIN_INSTRUCTIONS_TABLE);
|
|
40
35
|
const { custom: { [PLUGIN_NAME]: { editorConfig = {} } = {} } = {} } = collection?.admin ?? {};
|
|
41
36
|
const { schema: editorSchema = {} } = editorConfig;
|
|
@@ -89,8 +84,15 @@ export const useGenerate = ({ instructionId })=>{
|
|
|
89
84
|
onFinish: (result)=>{
|
|
90
85
|
if (result.object && field) {
|
|
91
86
|
if (field.type === 'richText') {
|
|
87
|
+
// Set state directly to editor first for immediate feedback
|
|
88
|
+
setSafeLexicalState(result.object, editor);
|
|
89
|
+
// Update history
|
|
92
90
|
setHistory(result.object);
|
|
93
|
-
setValue
|
|
91
|
+
// Delay Payload's setValue slightly to ensure editor stability
|
|
92
|
+
// and prevent field-level re-renders from interfering with the final state.
|
|
93
|
+
setTimeout(()=>{
|
|
94
|
+
setValue(result.object);
|
|
95
|
+
}, 150);
|
|
94
96
|
} else if ('name' in field) {
|
|
95
97
|
setHistory(result.object[field.name]);
|
|
96
98
|
setValue(result.object[field.name]);
|
|
@@ -101,23 +103,17 @@ export const useGenerate = ({ instructionId })=>{
|
|
|
101
103
|
},
|
|
102
104
|
schema: activeSchema
|
|
103
105
|
});
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return;
|
|
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,
|
|
106
|
+
// Hook 1: Handle high-frequency streaming updates
|
|
107
|
+
useStreamingUpdate({
|
|
117
108
|
editor,
|
|
118
|
-
|
|
109
|
+
isLoading: loadingObject,
|
|
110
|
+
object
|
|
111
|
+
});
|
|
112
|
+
// Hook 2: Handle Upload generation and polling
|
|
113
|
+
const { generateUpload, isJobActive, jobProgress, jobStatus } = useGenerateUpload({
|
|
114
|
+
instructionIdRef,
|
|
119
115
|
setValue
|
|
120
|
-
|
|
116
|
+
});
|
|
121
117
|
const streamObject = useCallback(({ action = 'Compose', params })=>{
|
|
122
118
|
const doc = getData();
|
|
123
119
|
const currentInstructionId = instructionIdRef.current;
|
|
@@ -138,103 +134,10 @@ export const useGenerate = ({ instructionId })=>{
|
|
|
138
134
|
}, [
|
|
139
135
|
localFromContext?.code,
|
|
140
136
|
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
137
|
documentId,
|
|
237
|
-
|
|
138
|
+
getData,
|
|
139
|
+
submit,
|
|
140
|
+
editor
|
|
238
141
|
]);
|
|
239
142
|
const generate = useCallback(async (options)=>{
|
|
240
143
|
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, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui'\nimport { jsonSchema } from 'ai'\nimport { useCallback, useMemo, useRef } from 'react'\n\nimport type { ActionMenuItems } from '../../../types.js'\n\nimport { PLUGIN_API_ENDPOINT_GENERATE, PLUGIN_INSTRUCTIONS_TABLE, PLUGIN_NAME } 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 { 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 const instructionIdRef = useRef(instructionId)\n // Sync ref\n instructionIdRef.current = instructionId\n\n const { field, path: pathFromContext } = useFieldProps()\n const { editor } = useEditorConfigContext()\n\n const { config } = useConfig()\n const { collections } = config\n\n // Use dispatchFields to update values without subscribing to them, avoiding re-renders during streaming\n // dispatchFields is handled inside useStreamingUpdate now, so we don't need it here\n // const { dispatchFields } = useForm()\n\n // Keep setValue for non-streaming updates (one-off), but do NOT destructure 'value' to avoid subscriptions\n const { setValue } = useField<any>({\n path: pathFromContext ?? '',\n })\n\n const { set: setHistory } = useHistory()\n const { getData } = useForm()\n const { id: documentId } = useDocumentInfo()\n const localFromContext = useLocale()\n\n // Editor Schema Setup\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 // Set state directly to editor first for immediate feedback\n setSafeLexicalState(result.object, editor)\n\n // Update history\n setHistory(result.object)\n\n // Delay Payload's setValue slightly to ensure editor stability\n // and prevent field-level re-renders from interfering with the final state.\n setTimeout(() => {\n setValue(result.object)\n }, 150)\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 // Hook 1: 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 setValue,\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","useConfig","useDocumentInfo","useField","useForm","useLocale","jsonSchema","useCallback","useMemo","useRef","PLUGIN_API_ENDPOINT_GENERATE","PLUGIN_INSTRUCTIONS_TABLE","PLUGIN_NAME","useFieldProps","editorSchemaValidator","fieldToJsonSchema","setSafeLexicalState","useGenerateUpload","useHistory","useStreamingUpdate","useGenerate","instructionId","instructionIdRef","current","field","path","pathFromContext","editor","config","collections","setValue","set","setHistory","getData","id","documentId","localFromContext","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","undefined","isLoading","loadingObject","object","stop","objectStop","submit","api","onError","message","console","onFinish","result","setTimeout","log","generateUpload","isJobActive","jobProgress","jobStatus","streamObject","action","params","doc","currentInstructionId","options","actionParams","allowedEditorNodes","Array","from","_nodes","locale","code","generate"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type UseGenerateUploadParams = {
|
|
2
|
+
instructionIdRef: React.MutableRefObject<string>;
|
|
3
|
+
setValue: (value: any) => void;
|
|
4
|
+
};
|
|
5
|
+
export declare const useGenerateUpload: ({ instructionIdRef, setValue }: UseGenerateUploadParams) => {
|
|
6
|
+
generateUpload: () => Promise<void | Response>;
|
|
7
|
+
isJobActive: boolean;
|
|
8
|
+
jobProgress: number;
|
|
9
|
+
jobStatus: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { toast, useConfig, useDocumentInfo, 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 { useHistory } from './useHistory.js';
|
|
5
|
+
export const useGenerateUpload = ({ instructionIdRef, setValue })=>{
|
|
6
|
+
const { config } = useConfig();
|
|
7
|
+
const { routes: { api }, serverURL } = config;
|
|
8
|
+
const { id: documentId, collectionSlug } = useDocumentInfo();
|
|
9
|
+
const localFromContext = useLocale();
|
|
10
|
+
const { getData } = useForm();
|
|
11
|
+
const { set: setHistory } = useHistory();
|
|
12
|
+
// Async job UI state
|
|
13
|
+
const [jobStatus, setJobStatus] = useState(undefined);
|
|
14
|
+
const [jobProgress, setJobProgress] = useState(0);
|
|
15
|
+
const [isJobActive, setIsJobActive] = useState(false);
|
|
16
|
+
const generateUpload = useCallback(async ()=>{
|
|
17
|
+
const doc = getData();
|
|
18
|
+
const currentInstructionId = instructionIdRef.current;
|
|
19
|
+
return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
collectionSlug: collectionSlug ?? '',
|
|
22
|
+
doc,
|
|
23
|
+
documentId,
|
|
24
|
+
locale: localFromContext?.code,
|
|
25
|
+
options: {
|
|
26
|
+
instructionId: currentInstructionId
|
|
27
|
+
}
|
|
28
|
+
}),
|
|
29
|
+
credentials: 'include',
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json'
|
|
32
|
+
},
|
|
33
|
+
method: 'POST'
|
|
34
|
+
}).then(async (uploadResponse)=>{
|
|
35
|
+
if (uploadResponse.ok) {
|
|
36
|
+
const json = await uploadResponse.json();
|
|
37
|
+
const { job, result } = json || {};
|
|
38
|
+
if (result) {
|
|
39
|
+
// Set the upload ID
|
|
40
|
+
setValue(result?.id);
|
|
41
|
+
setHistory(result?.id);
|
|
42
|
+
// Show toast to prompt user to save
|
|
43
|
+
toast.success('Image generated successfully! Click Save to see the preview.');
|
|
44
|
+
return uploadResponse;
|
|
45
|
+
}
|
|
46
|
+
// Async job: poll AI Jobs collection for status/progress/result_id
|
|
47
|
+
if (job && job.id) {
|
|
48
|
+
setIsJobActive(true);
|
|
49
|
+
const cancelled = false;
|
|
50
|
+
let attempts = 0;
|
|
51
|
+
const maxAttempts = 600 // up to ~10 minutes @ 1s
|
|
52
|
+
;
|
|
53
|
+
// Basic in-hook state via closure variables; UI will re-render off fetches below
|
|
54
|
+
const poll = async ()=>{
|
|
55
|
+
if (cancelled) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetch(`${serverURL}${api}/${PLUGIN_AI_JOBS_TABLE}/${job.id}`, {
|
|
60
|
+
credentials: 'include'
|
|
61
|
+
});
|
|
62
|
+
if (res.ok) {
|
|
63
|
+
const jobDoc = await res.json();
|
|
64
|
+
const { progress, result_id, status } = jobDoc || {};
|
|
65
|
+
setJobStatus(status);
|
|
66
|
+
setJobProgress(progress ?? 0);
|
|
67
|
+
// When result present, set field and finish
|
|
68
|
+
if (status === 'completed' && result_id) {
|
|
69
|
+
// Force upload field to refetch by clearing then setting the ID
|
|
70
|
+
setValue(null);
|
|
71
|
+
setTimeout(()=>{
|
|
72
|
+
setValue(result_id);
|
|
73
|
+
}, 0);
|
|
74
|
+
setHistory(result_id);
|
|
75
|
+
setIsJobActive(false);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (status === 'failed') {
|
|
79
|
+
setIsJobActive(false);
|
|
80
|
+
throw new Error('Video generation failed');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// silent retry
|
|
85
|
+
}
|
|
86
|
+
attempts += 1;
|
|
87
|
+
if (!cancelled && attempts < maxAttempts) {
|
|
88
|
+
setTimeout(poll, 1000);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
setTimeout(poll, 1000);
|
|
92
|
+
return uploadResponse;
|
|
93
|
+
}
|
|
94
|
+
throw new Error('generateUpload: Unexpected response');
|
|
95
|
+
} else {
|
|
96
|
+
const { errors = [] } = await uploadResponse.json();
|
|
97
|
+
const errStr = errors.map((error)=>error.message).join(', ');
|
|
98
|
+
throw new Error(errStr);
|
|
99
|
+
}
|
|
100
|
+
}).catch((error)=>{
|
|
101
|
+
toast.error(`Failed to generate: ${error.message}`);
|
|
102
|
+
console.error('Error generating or setting your upload, please set it manually if its saved in your media files.', error);
|
|
103
|
+
});
|
|
104
|
+
}, [
|
|
105
|
+
getData,
|
|
106
|
+
localFromContext?.code,
|
|
107
|
+
instructionIdRef,
|
|
108
|
+
setValue,
|
|
109
|
+
documentId,
|
|
110
|
+
collectionSlug,
|
|
111
|
+
serverURL,
|
|
112
|
+
api,
|
|
113
|
+
setHistory
|
|
114
|
+
]);
|
|
115
|
+
return {
|
|
116
|
+
generateUpload,
|
|
117
|
+
isJobActive,
|
|
118
|
+
jobProgress,
|
|
119
|
+
jobStatus
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
//# sourceMappingURL=useGenerateUpload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/Compose/hooks/useGenerateUpload.ts"],"sourcesContent":["import { toast, useConfig, useDocumentInfo, useForm, useLocale } from '@payloadcms/ui'\nimport { useCallback, useState } from 'react'\n\nimport type { GenerateTextarea } from '../../../types.js'\n\nimport { PLUGIN_AI_JOBS_TABLE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD } from '../../../defaults.js'\nimport { useHistory } from './useHistory.js'\n\ntype UseGenerateUploadParams = {\n instructionIdRef: React.MutableRefObject<string>\n setValue: (value: any) => void\n}\n\nexport const useGenerateUpload = ({ instructionIdRef, setValue }: UseGenerateUploadParams) => {\n const { config } = useConfig()\n const {\n routes: { api },\n serverURL,\n } = config\n const { id: documentId, collectionSlug } = useDocumentInfo()\n const localFromContext = useLocale()\n const { getData } = useForm()\n const { set: setHistory } = useHistory()\n\n // Async job UI state\n const [jobStatus, setJobStatus] = useState<string | undefined>(undefined)\n const [jobProgress, setJobProgress] = useState<number>(0)\n const [isJobActive, setIsJobActive] = useState<boolean>(false)\n\n const generateUpload = useCallback(async () => {\n const doc = getData()\n const currentInstructionId = instructionIdRef.current\n\n return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, {\n body: JSON.stringify({\n collectionSlug: collectionSlug ?? '',\n doc,\n documentId,\n locale: localFromContext?.code,\n options: {\n instructionId: currentInstructionId,\n },\n } satisfies Parameters<GenerateTextarea>[0]),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n .then(async (uploadResponse) => {\n if (uploadResponse.ok) {\n const json = await uploadResponse.json()\n const { job, result } = json || {}\n if (result) {\n // 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(`${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 }, [\n getData,\n localFromContext?.code,\n instructionIdRef,\n setValue,\n documentId,\n collectionSlug,\n serverURL,\n api,\n setHistory,\n ])\n\n return {\n generateUpload,\n isJobActive,\n jobProgress,\n jobStatus,\n }\n}\n"],"names":["toast","useConfig","useDocumentInfo","useForm","useLocale","useCallback","useState","PLUGIN_AI_JOBS_TABLE","PLUGIN_API_ENDPOINT_GENERATE_UPLOAD","useHistory","useGenerateUpload","instructionIdRef","setValue","config","routes","api","serverURL","id","documentId","collectionSlug","localFromContext","getData","set","setHistory","jobStatus","setJobStatus","undefined","jobProgress","setJobProgress","isJobActive","setIsJobActive","generateUpload","doc","currentInstructionId","current","fetch","body","JSON","stringify","locale","code","options","instructionId","credentials","headers","method","then","uploadResponse","ok","json","job","result","success","cancelled","attempts","maxAttempts","poll","res","jobDoc","progress","result_id","status","setTimeout","Error","e","errors","errStr","map","error","message","join","catch","console"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LexicalEditor } from 'lexical';
|
|
2
|
+
type UseStreamingUpdateParams = {
|
|
3
|
+
editor: LexicalEditor;
|
|
4
|
+
isLoading: boolean;
|
|
5
|
+
object: any;
|
|
6
|
+
};
|
|
7
|
+
export declare const useStreamingUpdate: ({ editor, isLoading, object }: UseStreamingUpdateParams) => void;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useForm } from '@payloadcms/ui';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js';
|
|
4
|
+
import { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js';
|
|
5
|
+
export const useStreamingUpdate = ({ editor, isLoading, object })=>{
|
|
6
|
+
const { field, path: pathFromContext } = useFieldProps();
|
|
7
|
+
const { dispatchFields } = useForm();
|
|
8
|
+
// Ref for latest object to avoid effect re-runs during high-frequency streaming
|
|
9
|
+
const objectRef = useRef(object);
|
|
10
|
+
objectRef.current = object;
|
|
11
|
+
useEffect(()=>{
|
|
12
|
+
// Only run the animation loop while loading (streaming)
|
|
13
|
+
if (!isLoading) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
let reqId;
|
|
17
|
+
const loop = ()=>{
|
|
18
|
+
const currentObject = objectRef.current;
|
|
19
|
+
if (currentObject) {
|
|
20
|
+
if (field?.type === 'richText') {
|
|
21
|
+
setSafeLexicalState(currentObject, editor);
|
|
22
|
+
} else if (field && 'name' in field && currentObject[field.name]) {
|
|
23
|
+
// Use dispatchFields for high-frequency streaming updates to avoid re-renders
|
|
24
|
+
dispatchFields({
|
|
25
|
+
type: 'UPDATE',
|
|
26
|
+
path: pathFromContext ?? '',
|
|
27
|
+
value: currentObject[field.name]
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Continue loop
|
|
32
|
+
reqId = requestAnimationFrame(loop);
|
|
33
|
+
};
|
|
34
|
+
// Start loop
|
|
35
|
+
loop();
|
|
36
|
+
return ()=>{
|
|
37
|
+
cancelAnimationFrame(reqId);
|
|
38
|
+
};
|
|
39
|
+
}, [
|
|
40
|
+
isLoading,
|
|
41
|
+
editor,
|
|
42
|
+
field,
|
|
43
|
+
dispatchFields,
|
|
44
|
+
pathFromContext
|
|
45
|
+
]);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=useStreamingUpdate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/Compose/hooks/useStreamingUpdate.ts"],"sourcesContent":["import { useForm } from '@payloadcms/ui'\nimport type { LexicalEditor } from 'lexical'\nimport { useEffect, useRef } from 'react'\n\nimport { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'\nimport { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js'\n\ntype UseStreamingUpdateParams = {\n editor: LexicalEditor\n isLoading: boolean\n object: any\n}\n\nexport const useStreamingUpdate = ({ editor, isLoading, object }: UseStreamingUpdateParams) => {\n const { field, path: pathFromContext } = useFieldProps()\n const { dispatchFields } = useForm()\n\n // Ref for latest object to avoid effect re-runs during high-frequency streaming\n const objectRef = useRef(object)\n objectRef.current = object\n\n useEffect(() => {\n // Only run the animation loop while loading (streaming)\n if (!isLoading) {\n return\n }\n\n let reqId: number\n\n const loop = () => {\n const currentObject = objectRef.current\n\n if (currentObject) {\n if (field?.type === 'richText') {\n setSafeLexicalState(currentObject, editor)\n } else if (field && 'name' in field && currentObject[field.name]) {\n // Use dispatchFields for high-frequency streaming updates to avoid re-renders\n dispatchFields({\n type: 'UPDATE',\n path: pathFromContext ?? '',\n value: currentObject[field.name],\n } as any)\n }\n }\n\n // Continue loop\n reqId = requestAnimationFrame(loop)\n }\n\n // Start loop\n loop()\n\n return () => {\n cancelAnimationFrame(reqId)\n }\n }, [isLoading, editor, field, dispatchFields, pathFromContext])\n}\n"],"names":["useForm","useEffect","useRef","useFieldProps","setSafeLexicalState","useStreamingUpdate","editor","isLoading","object","field","path","pathFromContext","dispatchFields","objectRef","current","reqId","loop","currentObject","type","name","value","requestAnimationFrame","cancelAnimationFrame"],"mappings":"AAAA,SAASA,OAAO,QAAQ,iBAAgB;AAExC,SAASC,SAAS,EAAEC,MAAM,QAAQ,QAAO;AAEzC,SAASC,aAAa,QAAQ,oDAAmD;AACjF,SAASC,mBAAmB,QAAQ,4CAA2C;AAQ/E,OAAO,MAAMC,qBAAqB,CAAC,EAAEC,MAAM,EAAEC,SAAS,EAAEC,MAAM,EAA4B;IACxF,MAAM,EAAEC,KAAK,EAAEC,MAAMC,eAAe,EAAE,GAAGR;IACzC,MAAM,EAAES,cAAc,EAAE,GAAGZ;IAE3B,gFAAgF;IAChF,MAAMa,YAAYX,OAAOM;IACzBK,UAAUC,OAAO,GAAGN;IAEpBP,UAAU;QACR,wDAAwD;QACxD,IAAI,CAACM,WAAW;YACd;QACF;QAEA,IAAIQ;QAEJ,MAAMC,OAAO;YACX,MAAMC,gBAAgBJ,UAAUC,OAAO;YAEvC,IAAIG,eAAe;gBACjB,IAAIR,OAAOS,SAAS,YAAY;oBAC9Bd,oBAAoBa,eAAeX;gBACrC,OAAO,IAAIG,SAAS,UAAUA,SAASQ,aAAa,CAACR,MAAMU,IAAI,CAAC,EAAE;oBAChE,8EAA8E;oBAC9EP,eAAe;wBACbM,MAAM;wBACNR,MAAMC,mBAAmB;wBACzBS,OAAOH,aAAa,CAACR,MAAMU,IAAI,CAAC;oBAClC;gBACF;YACF;YAEA,gBAAgB;YAChBJ,QAAQM,sBAAsBL;QAChC;QAEA,aAAa;QACbA;QAEA,OAAO;YACLM,qBAAqBP;QACvB;IACF,GAAG;QAACR;QAAWD;QAAQG;QAAOG;QAAgBD;KAAgB;AAChE,EAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useField } from '@payloadcms/ui';
|
|
3
|
+
import { Button, useField } from '@payloadcms/ui';
|
|
4
4
|
import React, { useState } from 'react';
|
|
5
5
|
export const EncryptedTextField = ({ label, path, required })=>{
|
|
6
6
|
const { setValue, value } = useField({
|
|
@@ -48,13 +48,13 @@ export const EncryptedTextField = ({ label, path, required })=>{
|
|
|
48
48
|
})
|
|
49
49
|
]
|
|
50
50
|
}),
|
|
51
|
-
/*#__PURE__*/ _jsx(
|
|
52
|
-
|
|
51
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
52
|
+
buttonStyle: "secondary",
|
|
53
53
|
onClick: ()=>{
|
|
54
54
|
setValue('');
|
|
55
55
|
setIsEditing(true);
|
|
56
56
|
},
|
|
57
|
-
|
|
57
|
+
size: "medium",
|
|
58
58
|
children: "Change"
|
|
59
59
|
})
|
|
60
60
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/EncryptedTextField/index.tsx"],"sourcesContent":["'use client'\n\nimport { useField } from '@payloadcms/ui'\nimport React, { useState } from 'react'\n\ntype Props = {\n label?: string\n path: string\n required?: boolean\n}\n\nexport const EncryptedTextField: React.FC<Props> = ({ label, path, required }) => {\n const { setValue, value } = useField<string>({ path })\n const [isEditing, setIsEditing] = useState(!value)\n\n const isMasked = typeof value === 'string' && value.startsWith('sk-') && value.includes('****')\n\n return (\n <div className=\"field-type text\">\n <label className=\"field-label\">\n {label || 'API Key'}\n {required && <span className=\"required\">*</span>}\n </label>\n\n {!isEditing && isMasked ? (\n <div style={{ alignItems: 'center', display: 'flex', gap: '10px' }}>\n <div\n style={{\n background: 'var(--theme-elevation-100)',\n borderRadius: '4px',\n flexGrow: 1,\n fontFamily: 'monospace',\n padding: '8px 12px',\n }}\n >\n {value}\n <span\n style={{ color: 'var(--theme-success-500)', fontSize: '0.8em', marginLeft: '10px' }}\n >\n ✓ Configured\n </span>\n </div>\n <
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/EncryptedTextField/index.tsx"],"sourcesContent":["'use client'\n\nimport { Button, useField } from '@payloadcms/ui'\nimport React, { useState } from 'react'\n\ntype Props = {\n label?: string\n path: string\n required?: boolean\n}\n\nexport const EncryptedTextField: React.FC<Props> = ({ label, path, required }) => {\n const { setValue, value } = useField<string>({ path })\n const [isEditing, setIsEditing] = useState(!value)\n\n const isMasked = typeof value === 'string' && value.startsWith('sk-') && value.includes('****')\n\n return (\n <div className=\"field-type text\">\n <label className=\"field-label\">\n {label || 'API Key'}\n {required && <span className=\"required\">*</span>}\n </label>\n\n {!isEditing && isMasked ? (\n <div style={{ alignItems: 'center', display: 'flex', gap: '10px' }}>\n <div\n style={{\n background: 'var(--theme-elevation-100)',\n borderRadius: '4px',\n flexGrow: 1,\n fontFamily: 'monospace',\n padding: '8px 12px',\n }}\n >\n {value}\n <span\n style={{ color: 'var(--theme-success-500)', fontSize: '0.8em', marginLeft: '10px' }}\n >\n ✓ Configured\n </span>\n </div>\n <Button\n buttonStyle=\"secondary\"\n onClick={() => {\n setValue('')\n setIsEditing(true)\n }}\n size=\"medium\"\n >\n Change\n </Button>\n </div>\n ) : (\n <input\n onChange={(e) => setValue(e.target.value)}\n placeholder=\"sk-...\"\n style={{ width: '100%' }}\n type=\"password\"\n value={value || ''}\n />\n )}\n </div>\n )\n}\n"],"names":["Button","useField","React","useState","EncryptedTextField","label","path","required","setValue","value","isEditing","setIsEditing","isMasked","startsWith","includes","div","className","span","style","alignItems","display","gap","background","borderRadius","flexGrow","fontFamily","padding","color","fontSize","marginLeft","buttonStyle","onClick","size","input","onChange","e","target","placeholder","width","type"],"mappings":"AAAA;;AAEA,SAASA,MAAM,EAAEC,QAAQ,QAAQ,iBAAgB;AACjD,OAAOC,SAASC,QAAQ,QAAQ,QAAO;AAQvC,OAAO,MAAMC,qBAAsC,CAAC,EAAEC,KAAK,EAAEC,IAAI,EAAEC,QAAQ,EAAE;IAC3E,MAAM,EAAEC,QAAQ,EAAEC,KAAK,EAAE,GAAGR,SAAiB;QAAEK;IAAK;IACpD,MAAM,CAACI,WAAWC,aAAa,GAAGR,SAAS,CAACM;IAE5C,MAAMG,WAAW,OAAOH,UAAU,YAAYA,MAAMI,UAAU,CAAC,UAAUJ,MAAMK,QAAQ,CAAC;IAExF,qBACE,MAACC;QAAIC,WAAU;;0BACb,MAACX;gBAAMW,WAAU;;oBACdX,SAAS;oBACTE,0BAAY,KAACU;wBAAKD,WAAU;kCAAW;;;;YAGzC,CAACN,aAAaE,yBACb,MAACG;gBAAIG,OAAO;oBAAEC,YAAY;oBAAUC,SAAS;oBAAQC,KAAK;gBAAO;;kCAC/D,MAACN;wBACCG,OAAO;4BACLI,YAAY;4BACZC,cAAc;4BACdC,UAAU;4BACVC,YAAY;4BACZC,SAAS;wBACX;;4BAECjB;0CACD,KAACQ;gCACCC,OAAO;oCAAES,OAAO;oCAA4BC,UAAU;oCAASC,YAAY;gCAAO;0CACnF;;;;kCAIH,KAAC7B;wBACC8B,aAAY;wBACZC,SAAS;4BACPvB,SAAS;4BACTG,aAAa;wBACf;wBACAqB,MAAK;kCACN;;;+BAKH,KAACC;gBACCC,UAAU,CAACC,IAAM3B,SAAS2B,EAAEC,MAAM,CAAC3B,KAAK;gBACxC4B,aAAY;gBACZnB,OAAO;oBAAEoB,OAAO;gBAAO;gBACvBC,MAAK;gBACL9B,OAAOA,SAAS;;;;AAK1B,EAAC"}
|