UNPKG

@ai-stack/payloadcms

Version:

<p align="center"> <img alt="Payload AI Plugin" src="assets/payload-ai-intro.gif" width="100%" /> </p>

203 lines (202 loc) 7.46 kB
'use client'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useEditorConfigContext } from '@payloadcms/richtext-lexical/client'; import { FieldDescription, Popup, useDocumentDrawer, useField } from '@payloadcms/ui'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { PLUGIN_INSTRUCTIONS_TABLE } from '../../defaults.js'; import { setSafeLexicalState } from '../../utilities/setSafeLexicalState.js'; import { PluginIcon } from '../Icons/Icons.js'; import { UndoRedoActions } from './UndoRedoActions.js'; import styles from './compose.module.css'; import { useMenu } from './hooks/menu/useMenu.js'; import { useGenerate } from './hooks/useGenerate.js'; function findParentWithClass(element, className) { // Base case: if the element is null, or we've reached the top of the DOM if (!element || element === document.body) { return null; } // Check if the current element has the class we're looking for if (element.classList.contains(className)) { return element; } // Recursively call the function on the parent element return findParentWithClass(element.parentElement, className); } export const Compose = ({ descriptionProps, instructionId, isConfigAllowed })=>{ const [DocumentDrawer, _, { closeDrawer, openDrawer }] = useDocumentDrawer({ id: instructionId, collectionSlug: PLUGIN_INSTRUCTIONS_TABLE }); const { field: { type: fieldType }, path: pathFromContext, schemaPath } = descriptionProps || {}; const { editor: lexicalEditor, editorContainerRef } = useEditorConfigContext(); // The below snippet is used to show/hide the action menu on AI-enabled fields const [input, setInput] = useState(null); const actionsRef = useRef(null); // Set input element for current field useEffect(()=>{ if (!actionsRef.current) return; const fieldId = `field-${pathFromContext.replace(/\./g, '__')}`; const inputElement = document.getElementById(fieldId); if (!inputElement && fieldType === 'richText') { setInput(editorContainerRef.current); } else { actionsRef.current.setAttribute('for', fieldId); setInput(inputElement); } }, [ pathFromContext, schemaPath, actionsRef, editorContainerRef ]); // Show or hide actions menu on field useEffect(()=>{ if (!input || !actionsRef.current) return; actionsRef.current.classList.add(styles.actions_hidden); // Create the handler function const clickHandler = (event)=>{ document.querySelectorAll('.ai-plugin-active')?.forEach((element)=>{ const actionElement = element.querySelector(`.${styles.actions}`); if (actionElement) { actionElement.classList.add(styles.actions_hidden); element.classList.remove('ai-plugin-active'); } }); actionsRef.current.classList.remove(styles.actions_hidden); const parentWithClass = findParentWithClass(event.target, 'field-type'); if (parentWithClass) { parentWithClass.classList.add('ai-plugin-active'); } }; // Add the event listener input.addEventListener('click', clickHandler); // Clean up the event listener when the component unmounts or input changes return ()=>{ input.removeEventListener('click', clickHandler); }; }, [ input, actionsRef ]); const [isProcessing, setIsProcessing] = useState(false); const { generate, isLoading, stop } = useGenerate({ instructionId }); const { ActiveComponent, Menu } = useMenu({ onCompose: async ()=>{ console.log('Composing...'); setIsProcessing(true); await generate({ action: 'Compose' }).finally(()=>{ setIsProcessing(false); }); }, onExpand: async ()=>{ console.log('Expanding...'); await generate({ action: 'Expand' }); }, onProofread: async ()=>{ console.log('Proofreading...'); await generate({ action: 'Proofread' }); }, onRephrase: async ()=>{ console.log('Rephrasing...'); await generate({ action: 'Rephrase' }); }, onSettings: isConfigAllowed ? openDrawer : undefined, onSimplify: async ()=>{ console.log('Simplifying...'); await generate({ action: 'Simplify' }); }, onSummarize: async ()=>{ console.log('Summarizing...'); await generate({ action: 'Summarize' }); }, onTranslate: async (data)=>{ console.log('Translating...'); await generate({ action: 'Translate', params: data }); } }, { isConfigAllowed }); const { setValue } = useField({ path: pathFromContext }); const setIfValueIsLexicalState = useCallback((val)=>{ if (val.root && lexicalEditor) { setSafeLexicalState(JSON.stringify(val), lexicalEditor); } // DO NOT PROVIDE lexicalEditor as a dependency, it freaks out and does not update the editor after first undo/redo }, []); const popupRender = useCallback(({ close })=>{ return /*#__PURE__*/ _jsx(Menu, { isLoading: isProcessing || isLoading, onClose: close }); }, [ isProcessing, isLoading, Menu ]); const memoizedPopup = useMemo(()=>{ return /*#__PURE__*/ _jsx(Popup, { button: /*#__PURE__*/ _jsx(PluginIcon, { isLoading: isProcessing || isLoading }), render: popupRender, verticalAlign: "bottom" }); }, [ popupRender, isProcessing, isLoading ]); return /*#__PURE__*/ _jsxs(React.Fragment, { children: [ /*#__PURE__*/ _jsxs("label", { className: `${styles.actions}`, onClick: (e)=>e.preventDefault(), ref: actionsRef, role: "presentation", children: [ /*#__PURE__*/ _jsx(DocumentDrawer, { onSave: ()=>{ closeDrawer(); } }), memoizedPopup, /*#__PURE__*/ _jsx(ActiveComponent, { isLoading: isProcessing || isLoading, stop: stop }), /*#__PURE__*/ _jsx(UndoRedoActions, { onChange: (val)=>{ setValue(val); setIfValueIsLexicalState(val); } }) ] }), descriptionProps ? /*#__PURE__*/ _jsx("div", { children: /*#__PURE__*/ _jsx(FieldDescription, { ...descriptionProps }) }) : null ] }); }; //# sourceMappingURL=Compose.js.map