UNPKG

@ai-stack/payloadcms

Version:

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

208 lines (207 loc) 7.58 kB
import { experimental_useObject as useObject } from '@ai-sdk/react'; import { useEditorConfigContext } from '@payloadcms/richtext-lexical/client'; import { toast, useConfig, useDocumentInfo, useField, useForm, useLocale } from '@payloadcms/ui'; import { jsonSchema } from 'ai'; import { useCallback, useEffect, useMemo, useRef } from 'react'; import { PLUGIN_API_ENDPOINT_GENERATE, PLUGIN_API_ENDPOINT_GENERATE_UPLOAD, PLUGIN_INSTRUCTIONS_TABLE, PLUGIN_NAME } from '../../../defaults.js'; import { useFieldProps } from '../../../providers/FieldProvider/useFieldProps.js'; import { editorSchemaValidator } from '../../../utilities/editorSchemaValidator.js'; import { fieldToJsonSchema } from '../../../utilities/fieldToJsonSchema.js'; import { setSafeLexicalState } from '../../../utilities/setSafeLexicalState.js'; import { useHistory } from './useHistory.js'; export const useGenerate = ({ instructionId })=>{ // Create a ref to hold the current instructionId const instructionIdRef = useRef(instructionId); // Update the ref whenever instructionId changes useEffect(()=>{ instructionIdRef.current = instructionId; }, [ instructionId ]); const { field, path: pathFromContext } = useFieldProps(); const editorConfigContext = useEditorConfigContext(); const { editor } = editorConfigContext; const { config } = useConfig(); const { routes: { api }, serverURL } = config; const { setValue } = useField({ path: pathFromContext ?? '' }); const { set: setHistory } = useHistory(); const { getData } = useForm(); const { id: documentId, collectionSlug } = useDocumentInfo(); const localFromContext = useLocale(); const { config: { collections } } = useConfig(); const collection = collections.find((collection)=>collection.slug === PLUGIN_INSTRUCTIONS_TABLE); const { custom: { [PLUGIN_NAME]: { editorConfig = {} } = {} } = {} } = collection?.admin ?? {}; const { schema: editorSchema = {} } = editorConfig; const memoizedValidator = useMemo(()=>{ return editorSchemaValidator(editorSchema); }, [ editorSchema ]); const memoizedSchema = useMemo(()=>jsonSchema(editorSchema, { validate: (value)=>{ const isValid = memoizedValidator(value); if (isValid) { return { success: true, value }; } else { return { error: new Error('Invalid schema'), success: false }; } } }), [ memoizedValidator ]); // Active JSON schema for useObject based on field type const activeSchema = useMemo(()=>{ const f = field; const fieldType = f?.type; if (fieldType === 'richText') { return memoizedSchema; } if (f && f.name && fieldType) { const schemaJson = fieldToJsonSchema(f); if (schemaJson && Object.keys(schemaJson).length > 0) { return jsonSchema(schemaJson); } } return undefined; }, [ field, memoizedSchema ]); const { isLoading: loadingObject, object, stop: objectStop, submit } = useObject({ api: `/api${PLUGIN_API_ENDPOINT_GENERATE}`, onError: (error)=>{ toast.error(`Failed to generate: ${error.message}`); console.error('Error generating object:', error); }, onFinish: (result)=>{ if (result.object && field) { if (field.type === 'richText') { setHistory(result.object); setValue(result.object); } else if ('name' in field) { setHistory(result.object[field.name]); setValue(result.object[field.name]); } } else { console.log('onFinish: result, field ', result, field); } }, schema: activeSchema }); useEffect(()=>{ if (!object) { return; } requestAnimationFrame(()=>{ if (field?.type === 'richText') { setSafeLexicalState(object, editor); } else if (field && 'name' in field && object[field.name]) { setValue(object[field.name]); } }); }, [ object, editor, field ]); const streamObject = useCallback(({ action = 'Compose', params })=>{ const doc = getData(); const currentInstructionId = instructionIdRef.current; const options = { action, actionParams: params, instructionId: currentInstructionId }; submit({ allowedEditorNodes: Array.from(editor?._nodes?.keys() || []), doc: { ...doc, id: documentId }, locale: localFromContext?.code, options }); }, [ localFromContext?.code, instructionIdRef, documentId ]); const generateUpload = useCallback(async ()=>{ const doc = getData(); const currentInstructionId = instructionIdRef.current; return fetch(`${serverURL}${api}${PLUGIN_API_ENDPOINT_GENERATE_UPLOAD}`, { body: JSON.stringify({ collectionSlug: collectionSlug ?? '', doc, documentId, locale: localFromContext?.code, options: { instructionId: currentInstructionId } }), credentials: 'include', headers: { 'Content-Type': 'application/json' }, method: 'POST' }).then(async (uploadResponse)=>{ if (uploadResponse.ok) { const { result } = await uploadResponse.json(); if (!result) { throw new Error('generateUpload: Something went wrong'); } setValue(result?.id); setHistory(result?.id); console.log('Image updated...', result); } else { const { errors = [] } = await uploadResponse.json(); const errStr = errors.map((error)=>error.message).join(', '); throw new Error(errStr); } return uploadResponse; }).catch((error)=>{ toast.error(`Failed to generate: ${error.message}`); console.error('Error generating or setting your upload, please set it manually if its saved in your media files.', error); }); }, [ getData, localFromContext?.code, instructionIdRef, setValue, documentId, collectionSlug ]); const generate = useCallback(async (options)=>{ if (field?.type === 'upload') { return generateUpload(); } // All supported types use structured object generation when schema is provided server-side return streamObject(options ?? { action: 'Compose' }); }, [ generateUpload, streamObject, field ]); const stop = useCallback(()=>{ console.log('Stopping...'); objectStop(); }, [ objectStop ]); return { generate, isLoading: loadingObject, stop }; }; //# sourceMappingURL=useGenerate.js.map